diff --git a/azurerm/internal/services/kusto/identity.go b/azurerm/internal/services/kusto/identity.go index 5188a1b90bc6..9f941b2d1614 100644 --- a/azurerm/internal/services/kusto/identity.go +++ b/azurerm/internal/services/kusto/identity.go @@ -1,6 +1,8 @@ package kusto import ( + "fmt" + "github.com/Azure/azure-sdk-for-go/services/kusto/mgmt/2020-09-18/kusto" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -22,44 +24,63 @@ func schemaIdentity() *schema.Schema { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice([]string{ - string(kusto.IdentityTypeNone), string(kusto.IdentityTypeSystemAssigned), + string(kusto.IdentityTypeUserAssigned), + string(kusto.IdentityTypeSystemAssignedUserAssigned), }, true), DiffSuppressFunc: suppress.CaseDifference, }, + + "identity_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: msivalidate.UserAssignedIdentityID, + }, + }, + "principal_id": { Type: schema.TypeString, Computed: true, }, + "tenant_id": { Type: schema.TypeString, Computed: true, }, - "identity_ids": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: msivalidate.UserAssignedIdentityID, - }, - }, }, }, } } -func expandIdentity(input []interface{}) *kusto.Identity { +func expandIdentity(input []interface{}) (*kusto.Identity, error) { if len(input) == 0 || input[0] == nil { - return nil + return &kusto.Identity{ + Type: kusto.IdentityTypeNone, + }, nil } - identity := input[0].(map[string]interface{}) - identityType := kusto.IdentityType(identity["type"].(string)) + raw := input[0].(map[string]interface{}) kustoIdentity := kusto.Identity{ - Type: identityType, + Type: kusto.IdentityType(raw["type"].(string)), + } + + identityIdsRaw := raw["identity_ids"].(*schema.Set).List() + identityIds := make(map[string]*kusto.IdentityUserAssignedIdentitiesValue) + for _, v := range identityIdsRaw { + identityIds[v.(string)] = &kusto.IdentityUserAssignedIdentitiesValue{} + } + + if len(identityIds) > 0 { + if kustoIdentity.Type != kusto.IdentityTypeUserAssigned && kustoIdentity.Type != kusto.IdentityTypeSystemAssignedUserAssigned { + return nil, fmt.Errorf("`identity_ids` can only be specified when `type` includes `UserAssigned`") + } + + kustoIdentity.UserAssignedIdentities = identityIds } - return &kustoIdentity + return &kustoIdentity, nil } func flattenIdentity(input *kusto.Identity) ([]interface{}, error) { diff --git a/azurerm/internal/services/kusto/kusto_cluster_resource.go b/azurerm/internal/services/kusto/kusto_cluster_resource.go index 88b696c2188f..a7261013eadc 100644 --- a/azurerm/internal/services/kusto/kusto_cluster_resource.go +++ b/azurerm/internal/services/kusto/kusto_cluster_resource.go @@ -15,6 +15,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/kusto/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -26,9 +27,10 @@ func resourceKustoCluster() *schema.Resource { Update: resourceKustoClusterCreateUpdate, Delete: resourceKustoClusterDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ClusterID(id) + return err + }), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(60 * time.Minute), @@ -76,6 +78,7 @@ func resourceKustoCluster() *schema.Resource { string(kusto.StandardE16aV4), string(kusto.StandardE2aV4), string(kusto.StandardE4aV4), + string(kusto.StandardE64iV3), string(kusto.StandardE8asV41TBPS), string(kusto.StandardE8asV42TBPS), string(kusto.StandardE8aV4), @@ -126,6 +129,12 @@ func resourceKustoCluster() *schema.Resource { }, }, + "double_encryption_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "enable_disk_encryption": { Type: schema.TypeBool, Optional: true, @@ -262,11 +271,12 @@ func resourceKustoClusterCreateUpdate(d *schema.ResourceData, meta interface{}) engine := kusto.EngineType(d.Get("engine").(string)) clusterProperties := kusto.ClusterProperties{ - OptimizedAutoscale: optimizedAutoScale, - EnableDiskEncryption: utils.Bool(d.Get("enable_disk_encryption").(bool)), - EnableStreamingIngest: utils.Bool(d.Get("enable_streaming_ingest").(bool)), - EnablePurge: utils.Bool(d.Get("enable_purge").(bool)), - EngineType: engine, + OptimizedAutoscale: optimizedAutoScale, + EnableDiskEncryption: utils.Bool(d.Get("enable_disk_encryption").(bool)), + EnableDoubleEncryption: utils.Bool(d.Get("double_encryption_enabled").(bool)), + EnableStreamingIngest: utils.Bool(d.Get("enable_streaming_ingest").(bool)), + EnablePurge: utils.Bool(d.Get("enable_purge").(bool)), + EngineType: engine, } if v, ok := d.GetOk("virtual_network_configuration"); ok { @@ -291,8 +301,11 @@ func resourceKustoClusterCreateUpdate(d *schema.ResourceData, meta interface{}) } if _, ok := d.GetOk("identity"); ok { - kustoIdentityRaw := d.Get("identity").([]interface{}) - kustoIdentity := expandIdentity(kustoIdentityRaw) + kustoIdentity, err := expandIdentity(d.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + kustoCluster.Identity = kustoIdentity } @@ -405,6 +418,7 @@ func resourceKustoClusterRead(d *schema.ResourceData, meta interface{}) error { } if clusterProperties := clusterResponse.ClusterProperties; clusterProperties != nil { + d.Set("double_encryption_enabled", clusterProperties.EnableDoubleEncryption) d.Set("trusted_external_tenants", flattenTrustedExternalTenants(clusterProperties.TrustedExternalTenants)) d.Set("enable_disk_encryption", clusterProperties.EnableDiskEncryption) d.Set("enable_streaming_ingest", clusterProperties.EnableStreamingIngest) diff --git a/azurerm/internal/services/kusto/kusto_cluster_resource_test.go b/azurerm/internal/services/kusto/kusto_cluster_resource_test.go index ac1c7051e1c5..8ca5bc046c19 100644 --- a/azurerm/internal/services/kusto/kusto_cluster_resource_test.go +++ b/azurerm/internal/services/kusto/kusto_cluster_resource_test.go @@ -71,6 +71,21 @@ func TestAccKustoCluster_update(t *testing.T) { }) } +func TestAccKustoCluster_doubleEncryption(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kusto_cluster", "test") + r := KustoClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.doubleEncryption(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccKustoCluster_withTags(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_kusto_cluster", "test") r := KustoClusterResource{} @@ -154,6 +169,40 @@ func TestAccKustoCluster_identitySystemAssigned(t *testing.T) { }) } +func TestAccKustoCluster_UserAssignedIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kusto_cluster", "test") + r := KustoClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.userAssignedIdentity(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"), + check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.principal_id").HasValue(""), + ), + }, + }) +} + +func TestAccKustoCluster_multipleAssignedIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kusto_cluster", "test") + r := KustoClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.multipleAssignedIdentity(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned, UserAssigned"), + check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"), + resource.TestMatchResourceAttr(data.ResourceName, "identity.0.principal_id", validate.UUIDRegExp), + ), + }, + }) +} + func TestAccKustoCluster_vnet(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_kusto_cluster", "test") r := KustoClusterResource{} @@ -288,6 +337,31 @@ resource "azurerm_kusto_cluster" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func (KustoClusterResource) doubleEncryption(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +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 + double_encryption_enabled = true + + sku { + name = "Dev(No SLA)_Standard_D11_v2" + capacity = 1 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + func (KustoClusterResource) withTags(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { @@ -450,6 +524,76 @@ resource "azurerm_kusto_cluster" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString) } +func (KustoClusterResource) userAssignedIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctest%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +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 + } + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString) +} + +func (KustoClusterResource) multipleAssignedIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctest%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +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 + } + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString) +} + func (KustoClusterResource) languageExtensions(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/kusto_cluster.html.markdown b/website/docs/r/kusto_cluster.html.markdown index 37a7bcb1205c..d6df76f9a745 100644 --- a/website/docs/r/kusto_cluster.html.markdown +++ b/website/docs/r/kusto_cluster.html.markdown @@ -46,7 +46,9 @@ The following arguments are supported: * `sku` - (Required) A `sku` block as defined below. -* `identity` - (Optional) A identity block. +* `double_encryption_enabled` - (Optional) Is the cluster's double encryption enabled? Defaults to `false`. Changing this forces a new resource to be created. + +* `identity` - (Optional) An identity block. * `enable_disk_encryption` - (Optional) Specifies if the cluster's disks are encrypted. @@ -72,7 +74,7 @@ The following arguments are supported: A `sku` block supports the following: -* `name` - (Required) The name of the SKU. Valid values are: `Dev(No SLA)_Standard_D11_v2`, `Dev(No SLA)_Standard_E2a_v4`, `Standard_D11_v2`, `Standard_D12_v2`, `Standard_D13_v2`, `Standard_D14_v2`, `Standard_DS13_v2+1TB_PS`, `Standard_DS13_v2+2TB_PS`, `Standard_DS14_v2+3TB_PS`, `Standard_DS14_v2+4TB_PS`, `Standard_E16as_v4+3TB_PS`, `Standard_E16as_v4+4TB_PS`, `Standard_E16a_v4`, `Standard_E2a_v4`, `Standard_E4a_v4`, `Standard_E8as_v4+1TB_PS`, `Standard_E8as_v4+2TB_PS`, `Standard_E8a_v4`, `Standard_L16s`, `Standard_L4s` and `Standard_L8s` +* `name` - (Required) The name of the SKU. Valid values are: `Dev(No SLA)_Standard_D11_v2`, `Dev(No SLA)_Standard_E2a_v4`, `Standard_D11_v2`, `Standard_D12_v2`, `Standard_D13_v2`, `Standard_D14_v2`, `Standard_DS13_v2+1TB_PS`, `Standard_DS13_v2+2TB_PS`, `Standard_DS14_v2+3TB_PS`, `Standard_DS14_v2+4TB_PS`, `Standard_E16as_v4+3TB_PS`, `Standard_E16as_v4+4TB_PS`, `Standard_E16a_v4`, `Standard_E2a_v4`, `Standard_E4a_v4`, `Standard_E64i_v3`, `Standard_E8as_v4+1TB_PS`, `Standard_E8as_v4+2TB_PS`, `Standard_E8a_v4`, `Standard_L16s`, `Standard_L4s` and `Standard_L8s`. * `capacity` - (Optional) Specifies the node count for the cluster. Boundaries depend on the sku name. @@ -93,15 +95,11 @@ A `virtual_network_configuration` block supports the following: An `identity` block supports the following: -* `type` - (Required) Specifies the type of Managed Service Identity that is configured on this Kusto Cluster. Possible values are: `SystemAssigned` (where Azure will generate a Service Principal for you). - -* `principal_id` - (Computed) Specifies the Principal ID of the System Assigned Managed Service Identity that is configured on this Kusto Cluster. - -* `tenant_id` - (Computed) Specifies the Tenant ID of the System Assigned Managed Service Identity that is configured on this Kusto Cluster. +* `type` - (Required) Specifies the type of Managed Service Identity that is configured on this Kusto Cluster. Possible values are: `SystemAssigned`, `UserAssigned` and `SystemAssigned, UserAssigned`. -* `identity_ids` - (Computed) The list of user identities associated with the Kusto cluster. +* `identity_ids` - (Optional) A list of IDs for User Assigned Managed Identity resources to be assigned. -~> **NOTE:** When `type` is set to `SystemAssigned`, the Principal ID can be retrieved after the cluster has been created. More details are available below. See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) for additional information. +~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. --- @@ -121,6 +119,16 @@ The following attributes are exported: * `data_ingestion_uri` - The Kusto Cluster URI to be used for data ingestion. +* `identity` - An `identity` block as defined below. + +--- + +An `identity` block exports the following: + +* `principal_id` - The Principal ID associated with this System Assigned Managed Service Identity. + +* `tenant_id` - The Tenant ID associated with this System Assigned Managed Service Identity. + ## Timeouts