From 97e4b03c19eecd36b167049835c7a8634a3183c1 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sat, 1 May 2021 23:08:05 +0200 Subject: [PATCH 01/33] Add support for Azure Machine Learning Inference Cluster Resource --- .../services/machinelearning/client/client.go | 20 +- ...ine_learning_inference_cluster_resource.go | 498 ++++++++++++++++++ ...earning_inference_cluster_resource_test.go | 379 +++++++++++++ .../parse/inference_cluster.go | 99 ++++ .../parse/inference_cluster_test.go | 185 +++++++ .../services/machinelearning/registration.go | 3 +- .../machinelearning/ssl_config/HOWTO.md | 4 + .../machinelearning/ssl_config/cert.pem | 22 + .../machinelearning/ssl_config/key.pem | 28 + .../validate/inference_cluster_name.go | 72 +++ .../validate/inference_cluster_name_test.go | 71 +++ ...e_learning_inference_cluster.html.markdown | 104 ++++ 12 files changed, 1482 insertions(+), 3 deletions(-) create mode 100644 azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go create mode 100644 azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go create mode 100644 azurerm/internal/services/machinelearning/parse/inference_cluster.go create mode 100644 azurerm/internal/services/machinelearning/parse/inference_cluster_test.go create mode 100644 azurerm/internal/services/machinelearning/ssl_config/HOWTO.md create mode 100644 azurerm/internal/services/machinelearning/ssl_config/cert.pem create mode 100644 azurerm/internal/services/machinelearning/ssl_config/key.pem create mode 100644 azurerm/internal/services/machinelearning/validate/inference_cluster_name.go create mode 100644 azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go create mode 100644 website/docs/r/machine_learning_inference_cluster.html.markdown diff --git a/azurerm/internal/services/machinelearning/client/client.go b/azurerm/internal/services/machinelearning/client/client.go index 1ed909526ffe..d29aaf6331a6 100644 --- a/azurerm/internal/services/machinelearning/client/client.go +++ b/azurerm/internal/services/machinelearning/client/client.go @@ -1,19 +1,35 @@ package client import ( + "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-02-01/containerservice" "github.com/Azure/azure-sdk-for-go/services/machinelearningservices/mgmt/2020-04-01/machinelearningservices" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" ) type Client struct { - WorkspacesClient *machinelearningservices.WorkspacesClient + WorkspacesClient *machinelearningservices.WorkspacesClient + MachineLearningComputeClient *machinelearningservices.MachineLearningComputeClient + KubernetesClustersClient *containerservice.ManagedClustersClient + AgentPoolsClient *containerservice.AgentPoolsClient } func NewClient(o *common.ClientOptions) *Client { WorkspacesClient := machinelearningservices.NewWorkspacesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&WorkspacesClient.Client, o.ResourceManagerAuthorizer) + MachineLearningComputeClient := machinelearningservices.NewMachineLearningComputeClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&MachineLearningComputeClient.Client, o.ResourceManagerAuthorizer) + + KubernetesClustersClient := containerservice.NewManagedClustersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&KubernetesClustersClient.Client, o.ResourceManagerAuthorizer) + + agentPoolsClient := containerservice.NewAgentPoolsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&agentPoolsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - WorkspacesClient: &WorkspacesClient, + WorkspacesClient: &WorkspacesClient, + MachineLearningComputeClient: &MachineLearningComputeClient, + KubernetesClustersClient: &KubernetesClustersClient, + AgentPoolsClient: &agentPoolsClient, } } diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go new file mode 100644 index 000000000000..5ca216bceddd --- /dev/null +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -0,0 +1,498 @@ +package machinelearning + +import ( + "fmt" + "math" + "time" + + "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-02-01/containerservice" + "github.com/Azure/azure-sdk-for-go/services/machinelearningservices/mgmt/2020-04-01/machinelearningservices" + + "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/clients" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/validate" + "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" +) + +const min_number_of_nodes_prod int32 = 12 + +func resourceAksInferenceCluster() *schema.Resource { + return &schema.Resource{ + Create: resourceAksInferenceClusterCreateUpdate, + Read: resourceAksInferenceClusterRead, + Update: resourceAksInferenceClusterCreateUpdate, + Delete: resourceAksInferenceClusterDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.InferenceClusterID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.InferenceClusterName, + }, + + "workspace_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.WorkspaceName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "kubernetes_cluster_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "kubernetes_cluster_rg": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.KubernetesClusterResourceGroupName, + }, + + "cluster_purpose": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "Dev", + ValidateFunc: validate.ClusterPurpose, + }, + + "node_pool_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NodePoolName, + }, + + "identity": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(machinelearningservices.SystemAssigned), + }, false), + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "ssl_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Default: false, + }, + + "ssl_certificate_custom": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cert": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "key": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "cname": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + }, + }, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + }, + + "resource_id": { + Type: schema.TypeString, + Computed: true, + }, + + "sku_name": { + Type: schema.TypeString, + Computed: true, + ForceNew: true, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interface{}) error { + machine_learning_workspaces_client := meta.(*clients.Client).MachineLearning.WorkspacesClient + machine_learning_compute_client := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient + kubernetes_clusters_client := meta.(*clients.Client).Containers.KubernetesClustersClient + node_pools_client := meta.(*clients.Client).Containers.AgentPoolsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + workspace_name := d.Get("workspace_name").(string) + resource_group_name := d.Get("resource_group_name").(string) + + existing, err := machine_learning_compute_client.Get(ctx, resource_group_name, workspace_name, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("error checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspace_name, resource_group_name, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_machine_learning_inference_cluster", *existing.ID) + } + + // Get SKU from Workspace + aml_ws, _ := machine_learning_workspaces_client.Get(ctx, resource_group_name, workspace_name) + + sku := aml_ws.Sku + + kubernetes_cluster_name := d.Get("kubernetes_cluster_name").(string) + kubernetes_cluster_rg := d.Get("kubernetes_cluster_rg").(string) + + // Get Existing AKS + aks_cluster, _ := kubernetes_clusters_client.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name) + + pool_name := d.Get("node_pool_name").(string) + node_pool, _ := node_pools_client.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name, pool_name) + + t := d.Get("tags").(map[string]interface{}) + + identity := d.Get("identity") + + ssl_config := expandSSLConfig(d) + cluster_purpose := d.Get("cluster_purpose").(string) + var map_cluster_purpose = map[string]string{ + "Dev": "DevTest", + "Test": "DevTest", + "Prod": "FastProd", + } + aks_cluster_purpose := machinelearningservices.ClusterPurpose(map_cluster_purpose[cluster_purpose]) + aks_properties := expandAksProperties(&aks_cluster, &node_pool, ssl_config, aks_cluster_purpose) + + location := azure.NormalizeLocation(d.Get("location").(string)) + description := d.Get("description").(string) + aks_compute_properties := expandAksComputeProperties(aks_properties, &aks_cluster, &node_pool, location, description) + compute_properties, is_aks := (machinelearningservices.BasicCompute).AsAKS(aks_compute_properties) + + if !is_aks { + return fmt.Errorf("error: No AKS cluster") + } + + inference_cluster_parameters := machinelearningservices.ComputeResource{ + // Properties - Compute properties + Properties: compute_properties, + // ID - READ-ONLY; Specifies the resource ID. + // Name - READ-ONLY; Specifies the name of the resource. + // Identity - The identity of the resource. + Identity: expandMachineLearningComputeClusterIdentity(identity.([]interface{})), + // Location - Specifies the location of the resource. + Location: &location, + // Type - READ-ONLY; Specifies the type of the resource. + // Tags - Contains resource tags defined as key/value pairs. + Tags: tags.Expand(t), + // Sku - The sku of the workspace. + Sku: sku, + } + + if v, ok := d.GetOk("description"); ok { + aks_compute_properties.Description = utils.String(v.(string)) + } + + future, ml_err := machine_learning_compute_client.CreateOrUpdate(ctx, resource_group_name, workspace_name, name, inference_cluster_parameters) + if ml_err != nil { + return fmt.Errorf("error creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, ml_err) + } + if err := future.WaitForCompletionRef(ctx, machine_learning_compute_client.Client); err != nil { + return fmt.Errorf("error waiting for creation of Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) + } + resp, ml_get_err := machine_learning_compute_client.Get(ctx, resource_group_name, workspace_name, name) + if ml_get_err != nil { + return fmt.Errorf("error retrieving Inference Cluster Compute %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, ml_get_err) + } + + if resp.ID == nil { + return fmt.Errorf("cannot read Inference Cluster ID %q in workspace %q (Resource Group %q) ID", name, workspace_name, resource_group_name) + } + + d.SetId(*resp.ID) + + return resourceAksInferenceClusterRead(d, meta) +} + +func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) error { + machine_learning_compute_client := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient + kubernetes_clusters_client := meta.(*clients.Client).Containers.KubernetesClustersClient + node_pools_client := meta.(*clients.Client).Containers.AgentPoolsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.InferenceClusterID(d.Id()) + if err != nil { + return fmt.Errorf("error parsing Inference Cluster ID `%q`: %+v", d.Id(), err) + } + + resp, err := machine_learning_compute_client.Get(ctx, id.ResourceGroup, id.Name, id.InferenceClusterName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("error making Read request on Inference Cluster %q in Workspace %q (Resource Group %q): %+v", id.InferenceClusterName, id.Name, id.ResourceGroup, err) + } + + d.Set("workspace_name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("name", id.InferenceClusterName) + + aks_resp, _ := kubernetes_clusters_client.ListByResourceGroup(ctx, id.ResourceGroup) + kubernetes_cluster_name := aks_resp.Values()[0].Name + resource_id := aks_resp.Values()[0].ID + + // get SSL configuration from ak1 below by getting an aks to pass to AsAKS by using the methods in create function... = + node_pool_list, _ := node_pools_client.List(ctx, id.ResourceGroup, *kubernetes_cluster_name) + pool_name := node_pool_list.Values()[0].Name + + d.Set("kubernetes_cluster_name", kubernetes_cluster_name) + d.Set("kubernetes_cluster_rg", id.ResourceGroup) + d.Set("resource_id", resource_id) + d.Set("node_pool_name", pool_name) + + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + + if sku := resp.Sku; sku != nil { + d.Set("sku_name", sku.Name) + } + + if err := d.Set("identity", flattenAksInferenceClusterIdentity(resp.Identity)); err != nil { + return fmt.Errorf("error flattening identity on Workspace %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) error { + machine_learning_compute_client := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + id, err := parse.InferenceClusterID(d.Id()) + if err != nil { + return fmt.Errorf("error parsing Inference Cluster ID `%q`: %+v", d.Id(), err) + } + underlying_resource_action := machinelearningservices.Detach + future, err := machine_learning_compute_client.Delete(ctx, id.ResourceGroup, id.Name, id.InferenceClusterName, underlying_resource_action) + if err != nil { + return fmt.Errorf("error deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.InferenceClusterName, id.Name, id.ResourceGroup, err) + } + if err := future.WaitForCompletionRef(ctx, machine_learning_compute_client.Client); err != nil { + return fmt.Errorf("error waiting for deletion of Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.InferenceClusterName, id.Name, id.ResourceGroup, err) + } + return nil +} + +func expandSSLConfig(d *schema.ResourceData) *machinelearningservices.SslConfiguration { + // Documentation: https://docs.microsoft.com/en-us/azure/machine-learning/how-to-secure-web-service + var map_ssl_enabled = map[bool]string{ + true: "Enabled", + false: "Disabled", + } + + // --- SSL Configurations --- + ssl_enabled := map_ssl_enabled[d.Get("ssl_enabled").(bool)] + + // SSL Certificate default values + cert_file := "" + key_file := "" + cname := "" + leaf_domain_label := "" + overwrite_existing_domain := false + + // SSL Custom Certificate settings + ssl_certificate_custom_refs := d.Get("ssl_certificate_custom").(*schema.Set).List() + + if len(ssl_certificate_custom_refs) > 0 && ssl_certificate_custom_refs[0] != nil { + ssl_certificate_custom_ref := ssl_certificate_custom_refs[0].(map[string]interface{}) + cert_file = ssl_certificate_custom_ref["cert"].(string) + key_file = ssl_certificate_custom_ref["key"].(string) + cname = ssl_certificate_custom_ref["cname"].(string) + } + + return &machinelearningservices.SslConfiguration{ + Status: machinelearningservices.Status1(ssl_enabled), + Cert: utils.String(cert_file), + Key: utils.String(key_file), + Cname: utils.String(cname), + LeafDomainLabel: utils.String(leaf_domain_label), + OverwriteExistingDomain: utils.Bool(overwrite_existing_domain)} +} + +func flattenCustomSSLConfig(ssl_config *machinelearningservices.SslConfiguration) []interface{} { + return []interface{}{ + map[string]interface{}{ + "cert": *(ssl_config.Cert), + "key": *(ssl_config.Key), + "cname": *(ssl_config.Cname), + }, + } +} + +func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node_pool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { + subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID //d.Get("subnet_id").(string) + service_cidr := *(aks.NetworkProfile.ServiceCidr) + dns_service_ip := *(aks.NetworkProfile.DNSServiceIP) + docker_bridge_cidr := *(aks.NetworkProfile.DockerBridgeCidr) + + return &machinelearningservices.AksNetworkingConfiguration{ + SubnetID: utils.String(subnet_id), + ServiceCidr: utils.String(service_cidr), + DNSServiceIP: utils.String(dns_service_ip), + DockerBridgeCidr: utils.String(docker_bridge_cidr)} +} + +func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool *containerservice.AgentPool, + ssl_config *machinelearningservices.SslConfiguration, cluster_purpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { + // https://github.com/Azure/azure-sdk-for-go/blob/v53.1.0/services/containerservice/mgmt/2020-12-01/containerservice/models.go#L1865 + fqdn := *(aks_cluster.ManagedClusterProperties.Fqdn) + agent_count := *(node_pool.ManagedClusterAgentPoolProfileProperties).Count + agent_vmsize := string(node_pool.ManagedClusterAgentPoolProfileProperties.VMSize) + + if agent_count < min_number_of_nodes_prod && cluster_purpose == "FastProd" { + min_number_of_cores_needed := int(math.Ceil(float64(min_number_of_nodes_prod) / float64(agent_count))) + err := fmt.Errorf("error: you should pick a VM with at least %d cores", min_number_of_cores_needed) + fmt.Println(err.Error()) + } + + return &machinelearningservices.AKSProperties{ + // ClusterFqdn - Cluster fully qualified domain name + ClusterFqdn: utils.String(fqdn), + // SystemServices - READ-ONLY; System services + // AgentCount - Number of agents + AgentCount: utils.Int32(agent_count), + // AgentVMSize - Agent virtual machine size + AgentVMSize: utils.String(agent_vmsize), + // SslConfiguration - SSL configuration + SslConfiguration: ssl_config, + // AksNetworkingConfiguration - AKS networking configuration for vnet + AksNetworkingConfiguration: expandAksNetworkingConfiguration(aks_cluster, node_pool), + // ClusterPurpose - Possible values include: 'FastProd', 'DenseProd', 'DevTest' + ClusterPurpose: cluster_purpose, + } +} + +func expandAksComputeProperties(aks_properties *machinelearningservices.AKSProperties, aks_cluster *containerservice.ManagedCluster, + node_pool *containerservice.AgentPool, location string, description string) machinelearningservices.AKS { + + return machinelearningservices.AKS{ + // Properties - AKS properties + Properties: aks_properties, + // ComputeLocation - Location for the underlying compute + ComputeLocation: &location, + // ProvisioningState - READ-ONLY; The provision state of the cluster. Valid values are Unknown, Updating, Provisioning, Succeeded, and Failed. Possible values include: 'ProvisioningStateUnknown', 'ProvisioningStateUpdating', 'ProvisioningStateCreating', 'ProvisioningStateDeleting', 'ProvisioningStateSucceeded', 'ProvisioningStateFailed', 'ProvisioningStateCanceled' + // Description - The description of the Machine Learning compute. + Description: &description, + // CreatedOn - READ-ONLY; The date and time when the compute was created. + // ModifiedOn - READ-ONLY; The date and time when the compute was last modified. + // ResourceID - ARM resource id of the underlying compute + ResourceID: aks_cluster.ID, + // ProvisioningErrors - READ-ONLY; Errors during provisioning + // IsAttachedCompute - READ-ONLY; Indicating whether the compute was provisioned by user and brought from outside if true, or machine learning service provisioned it if false. + // ComputeType - Possible values include: 'ComputeTypeCompute', 'ComputeTypeAKS1', 'ComputeTypeAmlCompute1', 'ComputeTypeVirtualMachine1', 'ComputeTypeHDInsight1', 'ComputeTypeDataFactory1', 'ComputeTypeDatabricks1', 'ComputeTypeDataLakeAnalytics1' + ComputeType: "ComputeTypeAKS1", + } +} + +func expandMachineLearningComputeClusterIdentity(input []interface{}) *machinelearningservices.Identity { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + identityType := machinelearningservices.ResourceIdentityType(v["type"].(string)) + + identity := machinelearningservices.Identity{ + Type: identityType, + } + + return &identity +} + +func flattenAksInferenceClusterIdentity(identity *machinelearningservices.Identity) []interface{} { + if identity == nil { + return []interface{}{} + } + + t := string(identity.Type) + + principalID := "" + if identity.PrincipalID != nil { + principalID = *identity.PrincipalID + } + + tenantID := "" + if identity.TenantID != nil { + tenantID = *identity.TenantID + } + + return []interface{}{ + map[string]interface{}{ + "type": t, + "principal_id": principalID, + "tenant_id": tenantID, + }, + } +} diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go new file mode 100644 index 000000000000..29d383e4d423 --- /dev/null +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -0,0 +1,379 @@ +package machinelearning_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type InferenceClusterResource struct{} + +func TestAccInferenceCluster_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") + r := InferenceClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep("ssl_enabled", "cluster_purpose", "description"), + }) +} + +func TestAccInferenceCluster_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") + r := InferenceClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccInferenceCluster_custom_ssl_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") + r := InferenceClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.custom_ssl_config_complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep("ssl_certificate_custom", "ssl_enabled", "cluster_purpose", "description"), + }) +} + +/* +func TestAccInferenceCluster_basicUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") + r := InferenceClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep("ssl_enabled", "cluster_purpose", "description"), + { + Config: r.basicUpdate(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep("ssl_enabled", "cluster_purpose", "description"), + }) +} +*/ + +/* +func TestAccInferenceCluster_completeUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") + r := InferenceClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.custom_ssl_config_complete(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep("ssl_certificate_custom", "ssl_enabled", "cluster_purpose", "description"), + { + Config: r.custom_ssl_config_completeUpdate(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep("ssl_certificate_custom", "ssl_enabled", "cluster_purpose", "description"), + }) +} +*/ + +func (r InferenceClusterResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { + inferenceClusterClient := client.MachineLearning.MachineLearningComputeClient + id, err := parse.InferenceClusterID(state.ID) + if err != nil { + return nil, err + } + + resp, err := inferenceClusterClient.Get(ctx, id.ResourceGroup, id.Name, id.InferenceClusterName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving Inference Cluster %q: %+v", state.ID, err) + } + + return utils.Bool(resp.Properties != nil), nil +} + +func (r InferenceClusterResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_machine_learning_inference_cluster" "test" { + name = "AIC-%d" + resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name + workspace_name = azurerm_machine_learning_workspace.test.name + location = azurerm_resource_group.test.location + kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name + kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name + cluster_purpose = "Dev" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomIntOfLength(8)) +} + +func (r InferenceClusterResource) basicUpdate(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_machine_learning_inference_cluster" "test" { + name = "AIC-%d" + resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name + workspace_name = azurerm_machine_learning_workspace.test.name + location = azurerm_resource_group.test.location + kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name + kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name + cluster_purpose = "DevTest" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + + identity { + type = "SystemAssigned" + } + + tags = { + ENV = "Test" + } +} +`, template, data.RandomIntOfLength(8)) +} + +func (r InferenceClusterResource) custom_ssl_config_complete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_machine_learning_inference_cluster" "test" { + name = "AIC-%d" + resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name + workspace_name = azurerm_machine_learning_workspace.test.name + location = azurerm_resource_group.test.location + kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name + kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name + cluster_purpose = "Test" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + ssl_enabled = true + ssl_certificate_custom { + cert = file("ssl_config/cert.pem") + key = file("ssl_config/key.pem") + cname = "www.contoso.com" + } + + identity { + type = "SystemAssigned" + } + +} +`, template, data.RandomIntOfLength(8)) +} + +func (r InferenceClusterResource) custom_ssl_config_completeUpdate(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_machine_learning_inference_cluster" "test" { + name = "AIC-%d" + resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name + workspace_name = azurerm_machine_learning_workspace.test.name + location = azurerm_resource_group.test.location + kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name + kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name + cluster_purpose = "Test" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + ssl_enabled = true + ssl_certificate_custom { + cert = file("ssl_config/cert.pem") + key = file("ssl_config/key.pem") + cname = "www.contoso.com" + } + + identity { + type = "SystemAssigned" + } + + tags = { + ENV = "Test" + } + +} +`, template, data.RandomIntOfLength(8)) +} + +func (r InferenceClusterResource) requiresImport(data acceptance.TestData) string { + template := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_machine_learning_inference_cluster" "import" { + name = azurerm_machine_learning_inference_cluster.test.name + resource_group_name = azurerm_machine_learning_inference_cluster.test.resource_group_name + workspace_name = azurerm_machine_learning_inference_cluster.test.workspace_name + location = azurerm_machine_learning_inference_cluster.test.location + kubernetes_cluster_name = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_name + kubernetes_cluster_rg = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_rg + node_pool_name = azurerm_machine_learning_inference_cluster.test.node_pool_name + cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose + + identity { + type = "SystemAssigned" + } + + tags = azurerm_machine_learning_inference_cluster.test.tags + } +`, template) +} + +func (r InferenceClusterResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-ml-%[1]d" + location = "%[2]s" +} + +resource "azurerm_application_insights" "test" { + name = "acctestai-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_key_vault" "test" { + name = "acctestvault%[3]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + + sku_name = "standard" + + purge_protection_enabled = true +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[4]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_machine_learning_workspace" "test" { + name = "acctest-MLW%[5]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_insights_id = azurerm_application_insights.test.id + key_vault_id = azurerm_key_vault.test.id + storage_account_id = azurerm_storage_account.test.id + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[6]d" + address_space = ["10.1.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%[7]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.1.0.0/24" +} + +resource "azurerm_kubernetes_cluster" "test" { + name = "acctestaks%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + dns_prefix = join("", ["acctestaks", azurerm_resource_group.test.location]) + node_resource_group = "acctestRGAKS-%d" + + default_node_pool { + name = "default" + node_count = 3 + vm_size = "Standard_D11_v2" + vnet_subnet_id = azurerm_subnet.test.id + } + + identity { + type = "SystemAssigned" + } +} +`, data.RandomInteger, data.Locations.Primary, + data.RandomIntOfLength(12), data.RandomIntOfLength(15), data.RandomIntOfLength(16), + data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/internal/services/machinelearning/parse/inference_cluster.go b/azurerm/internal/services/machinelearning/parse/inference_cluster.go new file mode 100644 index 000000000000..0e7222f20d4b --- /dev/null +++ b/azurerm/internal/services/machinelearning/parse/inference_cluster.go @@ -0,0 +1,99 @@ +package parse + +import ( + "fmt" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "strings" +) + +type InferenceClusterId struct { + Name string + ResourceGroup string + InferenceClusterName string +} + +func InferenceClusterID(input string) (*InferenceClusterId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + inference_cluster := InferenceClusterId{ + ResourceGroup: id.ResourceGroup, + } + + if inference_cluster.InferenceClusterName, err = id.PopSegment("computes"); err != nil { + return nil, err + } + + if inference_cluster.Name, err = id.PopSegment("workspaces"); err != nil { + return nil, err + } + + fmt.Printf("Debug: id = %q", id) + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &inference_cluster, nil +} + +type KubernetesClusterId struct { + SubscriptionId string + ResourceGroup string + ManagedClusterName string +} + +func NewClusterID(subscriptionId, resourceGroup, managedClusterName string) KubernetesClusterId { + return KubernetesClusterId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ManagedClusterName: managedClusterName, + } +} + +func (id KubernetesClusterId) String() string { + segments := []string{ + fmt.Sprintf("Managed Cluster Name %q", id.ManagedClusterName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Cluster", segmentsStr) +} + +func (id KubernetesClusterId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ManagedClusterName) +} + +// KubernetesClusterId parses a Cluster ID into an KubernetesClusterId struct +func KubernetesClusterID(input string) (*KubernetesClusterId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := KubernetesClusterId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.ManagedClusterName, err = id.PopSegment("managedClusters"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go b/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go new file mode 100644 index 000000000000..af664c0d71d8 --- /dev/null +++ b/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go @@ -0,0 +1,185 @@ +package parse + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +func TestInferenceClusterID(t *testing.T) { + testData := []struct { + Name string + Input string + Error bool + Expect *InferenceClusterId + }{ + { + Name: "Empty", + Input: "", + Error: true, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Error: true, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1", + Error: true, + }, + { + Name: "Missing Workspace Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/", + Error: true, + }, + { + Name: "Missing Machine Learning Workspace Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1", + Error: true, + }, + { + Name: "Machine Learning Inference Cluster ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1", + Error: false, + Expect: &InferenceClusterId{ + ResourceGroup: "resGroup1", + Name: "workspace1", + InferenceClusterName: "cluster1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := InferenceClusterID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expected a value but got an error: %+v", err) + } + + if actual.Name != v.Expect.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + } + + if actual.ResourceGroup != v.Expect.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) + } + + if actual.InferenceClusterName != v.Expect.InferenceClusterName { + t.Fatalf("Expected %q but got %q for InferenceClusterName", v.Expect.InferenceClusterName, actual.InferenceClusterName) + } + } +} + +var _ resourceid.Formatter = KubernetesClusterId{} + +func TestKubernetesClusterIDFormatter(t *testing.T) { + actual := NewClusterID("12345678-1234-9876-4563-123456789012", "resGroup1", "cluster1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestKubernetesClusterID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *KubernetesClusterId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing ManagedClusterName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", + Error: true, + }, + + { + // missing value for ManagedClusterName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1", + Expected: &KubernetesClusterId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ManagedClusterName: "cluster1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := KubernetesClusterID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.ManagedClusterName != v.Expected.ManagedClusterName { + t.Fatalf("Expected %q but got %q for ManagedClusterName", v.Expected.ManagedClusterName, actual.ManagedClusterName) + } + } +} diff --git a/azurerm/internal/services/machinelearning/registration.go b/azurerm/internal/services/machinelearning/registration.go index 7f91e9445cb1..f5b4cd9f8a18 100644 --- a/azurerm/internal/services/machinelearning/registration.go +++ b/azurerm/internal/services/machinelearning/registration.go @@ -27,6 +27,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_machine_learning_workspace": resourceMachineLearningWorkspace(), + "azurerm_machine_learning_workspace": resourceMachineLearningWorkspace(), + "azurerm_machine_learning_inference_cluster": resourceAksInferenceCluster(), } } diff --git a/azurerm/internal/services/machinelearning/ssl_config/HOWTO.md b/azurerm/internal/services/machinelearning/ssl_config/HOWTO.md new file mode 100644 index 000000000000..5f2a6218a43d --- /dev/null +++ b/azurerm/internal/services/machinelearning/ssl_config/HOWTO.md @@ -0,0 +1,4 @@ +# How Key and Certificate was generated +```bash +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem +``` \ No newline at end of file diff --git a/azurerm/internal/services/machinelearning/ssl_config/cert.pem b/azurerm/internal/services/machinelearning/ssl_config/cert.pem new file mode 100644 index 000000000000..a0334a6afa4d --- /dev/null +++ b/azurerm/internal/services/machinelearning/ssl_config/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkjCCAnoCCQDY1A4aUvTZ0TANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMC +Q0gxCzAJBgNVBAgMAlpIMQswCQYDVQQHDAJaSDESMBAGA1UECgwJVGVycmFmb3Jt +MQ4wDAYDVQQLDAVBenVyZTEYMBYGA1UEAwwPd3d3LmNvbnRvc28uY29tMSMwIQYJ +KoZIhvcNAQkBFhR3aGF0ZXZlckBjb250b3NvLmNvbTAeFw0yMTA0MjIxOTU4MTBa +Fw0zMTA0MjAxOTU4MTBaMIGKMQswCQYDVQQGEwJDSDELMAkGA1UECAwCWkgxCzAJ +BgNVBAcMAlpIMRIwEAYDVQQKDAlUZXJyYWZvcm0xDjAMBgNVBAsMBUF6dXJlMRgw +FgYDVQQDDA93d3cuY29udG9zby5jb20xIzAhBgkqhkiG9w0BCQEWFHdoYXRldmVy +QGNvbnRvc28uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5rx3 +fTN0UUV1ktetzM2AEIJ4ZKQlibrLtVORPX2LQp2Vl/n74DPD2Re/ZgO2NtjhjItY +O65ZSqOgGz3R8ED4r12AokLCFmqhBnnr4IybeaQos7prjLKwSIyj5NbVMGuzNO6P +55W1zTMfV+CstbCtXtRPa7zizXjYbT3dfpw8FgJLh9sVWaiCO34Nu9PWF9NRIlzI +e/Ek3ss/JnNqskH+xnxgxq68slaZa4qojBjiLl/IdIs4A9DtyJnFd99xuh8nShMg +4ykccPr9/+YBaz8/Ef7/zmXj3g9DLTrIa7JV6s80V5oVINaF7KXu9jmjD+a03SsR +/8eKX6K+xDBtqxpz8wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAOGe2knCVxje06 +ihfhzprg7lTM7GCgiXqa4fdCVwq0hJAYpMg29F7Df3OE/zVD/mzdRWZe2yVTY47f +YFEfDKMmkGepgqICs0wTfhBSham8vkk2yDcoT01Lar+Im3GToP3JSM5YFbqxam0R +/AVskE5aHQ+tIGUwcuwWhjjKQuWua59tI0USjgGaK3cZ5tyFOQPcE3ZFzndWM3Rz +ojNHH5UJOT7zt4RebBzGRpcNdrbkOtVkRVZIwH0wJfm44zR+L36UhpXUd8XGKvua +KFlqJhw/8UtYzXXX5bwHb/JTkOLUbs8gobG23lFhxXG5QhqtwqYnHXRw9Jhclv8p +weEgmhnj +-----END CERTIFICATE----- diff --git a/azurerm/internal/services/machinelearning/ssl_config/key.pem b/azurerm/internal/services/machinelearning/ssl_config/key.pem new file mode 100644 index 000000000000..87761f711640 --- /dev/null +++ b/azurerm/internal/services/machinelearning/ssl_config/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDmvHd9M3RRRXWS +163MzYAQgnhkpCWJusu1U5E9fYtCnZWX+fvgM8PZF79mA7Y22OGMi1g7rllKo6Ab +PdHwQPivXYCiQsIWaqEGeevgjJt5pCizumuMsrBIjKPk1tUwa7M07o/nlbXNMx9X +4Ky1sK1e1E9rvOLNeNhtPd1+nDwWAkuH2xVZqII7fg2709YX01EiXMh78STeyz8m +c2qyQf7GfGDGrryyVplriqiMGOIuX8h0izgD0O3ImcV333G6HydKEyDjKRxw+v3/ +5gFrPz8R/v/OZePeD0MtOshrslXqzzRXmhUg1oXspe72OaMP5rTdKxH/x4pfor7E +MG2rGnPzAgMBAAECggEAAXJvIWbgNN5FpX0axu0G/5OB48evwJReUK3MfGE8LVfF +p2VW8goBEWx3s9EUJHXpvDLng8BNKQ2rpGAX3/TYWmkwtFPM2c0jY2ICW68mDnY8 +Fxx1LjW0q0/Oe1HpllsmjY9tcZtbv4SxjqCHFMCd5blZIijWF0nJua2opPGf4tdv +yvN/D9HYPdRlynj6SjUij0rR2PFN134LLaKhRrAsaeKHqSk+Pngxt6HStRLPSnN+ +dqk+6rA0fJ97YXeiNRjYfRbJEMOedJFW5wavddIXkNzI/3iwrDc5P0A+9X+SbIGm +6BlKNy6EbFtEsbsbdcefZaVuaRXEVNsiRXBoUHjkYQKBgQD7yZWZDPr0JWtL+Mav +dewbQUd8xVMzAc9r2mjRCNJi5qanQRDNIDhkYkNVrpBnsKpCe8cDRb3SaawTqYEv +ASCPGOU92ooQ1AMKMwETaCsrSDAEwpS6NdCSuYZbu6yGqWc8/v4Oi8ZAv10I1TJP +2WaG2PkvfOpsvXs8ixQWZ4f5QwKBgQDqmLgV86pnDFUEfW8W8f0n6PG0gPSofcF7 +DKDEcRj3ZmkBWDUKiICBInrPgQaxw5rLA4lL1GwRgxMQg51fit52mQcsMK56/aQx +3BmSIoA3Uf+mzHp+bSL+o1vYmoOtklUF09DGIf+y4XyQy9GjojSzxCVkXqBwFldj +9+jL0NXXkQKBgECyo8YYF8P0eYWj/ynG20yFkaD181L//BRyosxTv/u52MjRZ0fO +J69jsHmryV9bfeRnedPVb9lJXfYPcCpr17ntY7ppFWENmVpdkMEz2yPcALq4ZQ8U +FOwez+9yYfqYPPbnbtC+CctJYNaMMcliy32K8zzIlFQsvCXqdtbq832RAoGAAtPw +dCNJzJAzfihc7HPiT1bZgwmC6X0Klgci8PtEB8duQJvll8jpc6UMwe+WOxJWjVfv +kcBvxQ5Fbo+HmB0+bUOO+JNlpwnjrs4uaLqNvRz57fLNDzUVlOg3NTc3myIGcFmL +TLggMvHQ5JXwYv6TkA8vPDR/zpoWV5gncD2GNmECgYEA8e9f30xeVtce5eUebRkB +bNCxi1sApTIPq8CXRN5JzX5plFj7K1HUlgqQsIxpdWJhi8G7DMj8C4/K7V+PjCTo +dU1ulbuFWwrIuSS3W6S1gh+eBhODfU80iO6SvSbGLiq11iRrQL/xMsCLgOExZE5d +BXgz1uzIrvJt5jmZh6bPSYc= +-----END PRIVATE KEY----- diff --git a/azurerm/internal/services/machinelearning/validate/inference_cluster_name.go b/azurerm/internal/services/machinelearning/validate/inference_cluster_name.go new file mode 100644 index 000000000000..b46d7c6599cc --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/inference_cluster_name.go @@ -0,0 +1,72 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func InferenceClusterName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + // The portal says: The workspace name must be between 1 and 16 characters. The name may only include alphanumeric characters and '-'. + // If you provide invalid name, the rest api will return an error with the following regex. + if matched := regexp.MustCompile(`^[a-zA-Z0-9][\w-]{1,15}$`).Match([]byte(v)); !matched { + errors = append(errors, fmt.Errorf("%s must be between 2 and 16 characters, and may only include alphanumeric characters and '-' character", k)) + } + return +} + +func KubernetesClusterResourceGroupName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + // Azure portal (not https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules) says: + // name for managed clusters have to be between 1 and 90 characters. The name may only include alphanumeric characters and '-, _'. + // start and end has to be alphanumeric. If you provide invalid name, the rest api will return an error with the following regex. + if matched := regexp.MustCompile(`^[a-zA-Z0-9][\w-_]{1,90}$`).Match([]byte(v)); !matched { + errors = append(errors, fmt.Errorf("%s must be between 1 and 90 characters, and may only include alphanumeric characters and '-, :' character", k)) + } + return +} + +func NodePoolName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if matched := regexp.MustCompile(`^[a-zA-Z0-9][\w-_]{1,12}$`).Match([]byte(v)); !matched { + errors = append(errors, fmt.Errorf("%s must be between 1 and 12 characters, and may only include alphanumeric characters and '-, :' character", k)) + } + return +} + +func ClusterPurpose(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + // Azure portal (not https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules) says: + // name for managed clusters have to be between 1 and 90 characters. The name may only include alphanumeric characters and '-, _'. + // start and end has to be alphanumeric. If you provide invalid name, the rest api will return an error with the following regex. + + switch v { + case + "Prod", + "Dev", + "Test": + return + } + errors = append(errors, fmt.Errorf("%s must be one of \"Prod\", \"Dev\", \"Test\" ", k)) + return +} diff --git a/azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go b/azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go new file mode 100644 index 000000000000..74490af4e7da --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go @@ -0,0 +1,71 @@ +package validate + +import "testing" + +func TestInferenceClusterName(t *testing.T) { + testData := []struct { + input string + expected bool + }{ + { + // empty + input: "", + expected: false, + }, + { + // basic example + input: "hello", + expected: true, + }, + { + // cannot start with a hyphen + input: "-hello", + expected: false, + }, + { + // can end with a hyphen + input: "hello-", + expected: true, + }, + { + // cannot contain other special symbols other than hyphens + input: "hello.world", + expected: false, + }, + { + // hyphen in the middle + input: "hello-world", + expected: true, + }, + { + // 1 char + input: "a", + expected: false, + }, + { + // 2 chars + input: "ab", + expected: true, + }, + { + // 16 chars + input: "abcdefghijklmnop", + expected: true, + }, + { + // 17 chars + input: "abcdefghijklmnopq", + expected: false, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.input) + + _, errors := InferenceClusterName(v.input, "name") + actual := len(errors) == 0 + if v.expected != actual { + t.Fatalf("Expected %t but got %t", v.expected, actual) + } + } +} diff --git a/website/docs/r/machine_learning_inference_cluster.html.markdown b/website/docs/r/machine_learning_inference_cluster.html.markdown new file mode 100644 index 000000000000..52a52ae8dff6 --- /dev/null +++ b/website/docs/r/machine_learning_inference_cluster.html.markdown @@ -0,0 +1,104 @@ +--- +subcategory: "Machine Learning" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_machine_learning_inference_cluster" +description: |- + Manages a Machine Learning Inference Cluster. +--- + +# azurerm_machine_learning_inference_cluster + +Manages a Machine Learning Inference Cluster. + +## Example Usage + +```hcl +resource "azurerm_machine_learning_inference_cluster" "example" { + name = "cluster-name" + resource_group_name = "cluster-rg" + location = "West Europe" + workspace_name = "aml-ws" + + identity { + type = "SystemAssigned" + } + kubernetes_cluster_name = "k8s-cluster-name" + node_pool_name = "default" + kubernetes_cluster_rg = "k8s-cluster-rg" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `identity` - (Required) A `identity` block as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `kubernetes_cluster_name` - (Required) The name of the Kubernetes Cluster resource to which to attach the inference cluster to. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `kubernetes_cluster_rg` - (Required) The name of the resource group in which the Kubernetes Cluster resides. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `location` - (Required) The Azure Region where the Machine Learning Inference Cluster should exist. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `name` - (Required) The name which should be used for this Machine Learning Inference Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `node_pool_name` - (Required) The name of the Kubernetes Cluster's node pool. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Machine Learning Inference Cluster should exist. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `workspace_name` - (Required) The name of the Azure Machine Learning Workspace where the Machine Learning Inference Cluster should exist. Changing this forces a new Machine Learning Inference Cluster to be created. + +--- + +* `cluster_purpose` - (Optional) The purpose of the Inference Cluster. If used for Development or Testing, use "Dev" or "Test" here. If using for production use "Prod" here. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `description` - (Optional) The description of the Machine Learning compute. + +* `ssl_certificate_custom` - (Optional) One or more `ssl_certificate_custom` blocks as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. + +* `ssl_enabled` - (Optional) Should the SSL Configuration be enabled? + +* `tags` - (Optional) A mapping of tags which should be assigned to the Machine Learning Inference Cluster. + +--- + +A `identity` block supports the following: + +* `type` - (Required) The Type of Identity which should be used for this Disk Encryption Set. At this time the only possible value is `SystemAssigned`. + +--- + +A `ssl_certificate_custom` block (maximally *one*) supports the following: + +* `cert` - (Optional) The content of the custom SSL certificate. + +* `cname` - (Optional) The Cname of the custom SSL certificate. + +* `key` - (Optional) The content of the key file. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Machine Learning Inference Cluster. + +* `resource_id` - The ID of the Machine Learning Inference Cluster. + +* `sku_name` - The type of SKU. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Machine Learning Inference Cluster. +* `read` - (Defaults to 5 minutes) Used when retrieving the Machine Learning Inference Cluster. +* `update` - (Defaults to 30 minutes) Used when updating the Machine Learning Inference Cluster. +* `delete` - (Defaults to 30 minutes) Used when deleting the Machine Learning Inference Cluster. + +## Import + +Machine Learning Inference Clusters can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_machine_learning_inference_cluster.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1 +``` \ No newline at end of file From 94639c6f10d273ac202d7f2bf4f8fae6ec700916 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Wed, 5 May 2021 12:05:04 +0200 Subject: [PATCH 02/33] Changes due to review#1 --- ...ine_learning_inference_cluster_resource.go | 239 +++++++++--------- ...earning_inference_cluster_resource_test.go | 117 ++++----- .../parse/inference_cluster.go | 4 +- .../parse/inference_cluster_test.go | 6 +- .../{ssl_config => testdata}/HOWTO.md | 0 .../{ssl_config => testdata}/cert.pem | 0 .../{ssl_config => testdata}/key.pem | 0 7 files changed, 180 insertions(+), 186 deletions(-) rename azurerm/internal/services/machinelearning/{ssl_config => testdata}/HOWTO.md (100%) rename azurerm/internal/services/machinelearning/{ssl_config => testdata}/cert.pem (100%) rename azurerm/internal/services/machinelearning/{ssl_config => testdata}/key.pem (100%) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 5ca216bceddd..9ada7f59d105 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -53,30 +53,20 @@ func resourceAksInferenceCluster() *schema.Resource { ValidateFunc: validate.InferenceClusterName, }, - "workspace_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validate.WorkspaceName, + "machine_learning_workspace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, - "resource_group_name": azure.SchemaResourceGroupName(), - "location": azure.SchemaLocation(), - "kubernetes_cluster_name": { + "kubernetes_cluster_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "kubernetes_cluster_rg": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validate.KubernetesClusterResourceGroupName, - }, - "cluster_purpose": { Type: schema.TypeString, Optional: true, @@ -118,14 +108,7 @@ func resourceAksInferenceCluster() *schema.Resource { }, }, - "ssl_enabled": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Default: false, - }, - - "ssl_certificate_custom": { + "ssl": { Type: schema.TypeSet, Optional: true, ForceNew: true, @@ -156,11 +139,6 @@ func resourceAksInferenceCluster() *schema.Resource { Optional: true, }, - "resource_id": { - Type: schema.TypeString, - Computed: true, - }, - "sku_name": { Type: schema.TypeString, Computed: true, @@ -173,18 +151,28 @@ func resourceAksInferenceCluster() *schema.Resource { } func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interface{}) error { - machine_learning_workspaces_client := meta.(*clients.Client).MachineLearning.WorkspacesClient - machine_learning_compute_client := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient - kubernetes_clusters_client := meta.(*clients.Client).Containers.KubernetesClustersClient - node_pools_client := meta.(*clients.Client).Containers.AgentPoolsClient + mlWorkspacesClient := meta.(*clients.Client).MachineLearning.WorkspacesClient + mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient + aksClient := meta.(*clients.Client).Containers.KubernetesClustersClient + poolsClient := meta.(*clients.Client).Containers.AgentPoolsClient ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() + // Define Inference Cluster Name name := d.Get("name").(string) - workspace_name := d.Get("workspace_name").(string) - resource_group_name := d.Get("resource_group_name").(string) - existing, err := machine_learning_compute_client.Get(ctx, resource_group_name, workspace_name, name) + // Get Machine Learning Workspace Name and Resource Group from ID + ml_workspace_id := d.Get("machine_learning_workspace_id").(string) + ws_id, err := parse.WorkspaceID(ml_workspace_id) + if err != nil { + return err + } + + workspace_name := ws_id.Name + resource_group_name := ws_id.ResourceGroup + + // Check if Inference Cluster already exists + existing, err := mlComputeClient.Get(ctx, resource_group_name, workspace_name, name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("error checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspace_name, resource_group_name, err) @@ -195,24 +183,38 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf } // Get SKU from Workspace - aml_ws, _ := machine_learning_workspaces_client.Get(ctx, resource_group_name, workspace_name) - + aml_ws, err := mlWorkspacesClient.Get(ctx, resource_group_name, workspace_name) + if err != nil { + return err + } sku := aml_ws.Sku - kubernetes_cluster_name := d.Get("kubernetes_cluster_name").(string) - kubernetes_cluster_rg := d.Get("kubernetes_cluster_rg").(string) + // Get Kubernetes Cluster Name and Resource Group from ID + aks_id := d.Get("kubernetes_cluster_id").(string) + aks_id_details, err := parse.KubernetesClusterID(aks_id) + if err != nil { + return err + } + kubernetes_cluster_name := aks_id_details.ManagedClusterName + kubernetes_cluster_rg := aks_id_details.ResourceGroup // Get Existing AKS - aks_cluster, _ := kubernetes_clusters_client.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name) + aks_cluster, err := aksClient.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name) + if err != nil { + return err + } pool_name := d.Get("node_pool_name").(string) - node_pool, _ := node_pools_client.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name, pool_name) + node_pool, err := poolsClient.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name, pool_name) + if err != nil { + return err + } t := d.Get("tags").(map[string]interface{}) - identity := d.Get("identity") + identity := d.Get("identity").([]interface{}) + ssl := expandSSLConfig(d) - ssl_config := expandSSLConfig(d) cluster_purpose := d.Get("cluster_purpose").(string) var map_cluster_purpose = map[string]string{ "Dev": "DevTest", @@ -220,13 +222,12 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf "Prod": "FastProd", } aks_cluster_purpose := machinelearningservices.ClusterPurpose(map_cluster_purpose[cluster_purpose]) - aks_properties := expandAksProperties(&aks_cluster, &node_pool, ssl_config, aks_cluster_purpose) + aks_properties := expandAksProperties(&aks_cluster, &node_pool, ssl, aks_cluster_purpose) location := azure.NormalizeLocation(d.Get("location").(string)) description := d.Get("description").(string) aks_compute_properties := expandAksComputeProperties(aks_properties, &aks_cluster, &node_pool, location, description) compute_properties, is_aks := (machinelearningservices.BasicCompute).AsAKS(aks_compute_properties) - if !is_aks { return fmt.Errorf("error: No AKS cluster") } @@ -237,7 +238,7 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf // ID - READ-ONLY; Specifies the resource ID. // Name - READ-ONLY; Specifies the name of the resource. // Identity - The identity of the resource. - Identity: expandMachineLearningComputeClusterIdentity(identity.([]interface{})), + Identity: expandAksInferenceClusterIdentity(identity), // Location - Specifies the location of the resource. Location: &location, // Type - READ-ONLY; Specifies the type of the resource. @@ -251,16 +252,16 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf aks_compute_properties.Description = utils.String(v.(string)) } - future, ml_err := machine_learning_compute_client.CreateOrUpdate(ctx, resource_group_name, workspace_name, name, inference_cluster_parameters) - if ml_err != nil { - return fmt.Errorf("error creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, ml_err) + future, err := mlComputeClient.CreateOrUpdate(ctx, resource_group_name, workspace_name, name, inference_cluster_parameters) + if err != nil { + return fmt.Errorf("error creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) } - if err := future.WaitForCompletionRef(ctx, machine_learning_compute_client.Client); err != nil { + if err := future.WaitForCompletionRef(ctx, mlComputeClient.Client); err != nil { return fmt.Errorf("error waiting for creation of Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) } - resp, ml_get_err := machine_learning_compute_client.Get(ctx, resource_group_name, workspace_name, name) - if ml_get_err != nil { - return fmt.Errorf("error retrieving Inference Cluster Compute %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, ml_get_err) + resp, err := mlComputeClient.Get(ctx, resource_group_name, workspace_name, name) + if err != nil { + return fmt.Errorf("error retrieving Inference Cluster Compute %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) } if resp.ID == nil { @@ -273,9 +274,10 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf } func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) error { - machine_learning_compute_client := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient - kubernetes_clusters_client := meta.(*clients.Client).Containers.KubernetesClustersClient - node_pools_client := meta.(*clients.Client).Containers.AgentPoolsClient + mlWorkspacesClient := meta.(*clients.Client).MachineLearning.WorkspacesClient + mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient + aksClient := meta.(*clients.Client).Containers.KubernetesClustersClient + poolsClient := meta.(*clients.Client).Containers.AgentPoolsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -284,49 +286,68 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("error parsing Inference Cluster ID `%q`: %+v", d.Id(), err) } - resp, err := machine_learning_compute_client.Get(ctx, id.ResourceGroup, id.Name, id.InferenceClusterName) + d.Set("name", id.InferenceClusterName) + + // Check that Inference Cluster Response can be read + resp, err := mlComputeClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.InferenceClusterName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("error making Read request on Inference Cluster %q in Workspace %q (Resource Group %q): %+v", id.InferenceClusterName, id.Name, id.ResourceGroup, err) + return fmt.Errorf("error making Read request on Inference Cluster %q in Workspace %q (Resource Group %q): %+v", + id.InferenceClusterName, id.WorkspaceName, id.ResourceGroup, err) } - d.Set("workspace_name", id.Name) - d.Set("resource_group_name", id.ResourceGroup) - d.Set("name", id.InferenceClusterName) + // Retrieve Machine Learning Workspace ID + ws_resp, err := mlWorkspacesClient.Get(ctx, id.ResourceGroup, id.WorkspaceName) + if err != nil { + return err + } + d.Set("machine_learning_workspace_id", *ws_resp.ID) - aks_resp, _ := kubernetes_clusters_client.ListByResourceGroup(ctx, id.ResourceGroup) - kubernetes_cluster_name := aks_resp.Values()[0].Name - resource_id := aks_resp.Values()[0].ID + // Retrieve AKS Cluster ID + aks_resp, err := aksClient.ListByResourceGroup(ctx, id.ResourceGroup) + if err != nil { + return err + } + + aks_id := *(aks_resp.Values()[0].ID) + + // Retrieve AKS Cluster name and Node pool name from ID + aks_id_details, err := parse.KubernetesClusterID(aks_id) + if err != nil { + return err + } - // get SSL configuration from ak1 below by getting an aks to pass to AsAKS by using the methods in create function... = - node_pool_list, _ := node_pools_client.List(ctx, id.ResourceGroup, *kubernetes_cluster_name) + kubernetes_cluster_name := aks_id_details.ManagedClusterName + node_pool_list, _ := poolsClient.List(ctx, id.ResourceGroup, kubernetes_cluster_name) pool_name := node_pool_list.Values()[0].Name - d.Set("kubernetes_cluster_name", kubernetes_cluster_name) - d.Set("kubernetes_cluster_rg", id.ResourceGroup) - d.Set("resource_id", resource_id) + d.Set("kubernetes_cluster_id", aks_id) d.Set("node_pool_name", pool_name) + // Retrieve location if location := resp.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } + // Retrieve Sku if sku := resp.Sku; sku != nil { d.Set("sku_name", sku.Name) } + // Retrieve Identity if err := d.Set("identity", flattenAksInferenceClusterIdentity(resp.Identity)); err != nil { - return fmt.Errorf("error flattening identity on Workspace %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + return fmt.Errorf("error flattening identity on Workspace %q (Resource Group %q): %+v", + id.WorkspaceName, id.ResourceGroup, err) } return tags.FlattenAndSet(d, resp.Tags) } func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) error { - machine_learning_compute_client := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient + mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() id, err := parse.InferenceClusterID(d.Id()) @@ -334,62 +355,52 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("error parsing Inference Cluster ID `%q`: %+v", d.Id(), err) } underlying_resource_action := machinelearningservices.Detach - future, err := machine_learning_compute_client.Delete(ctx, id.ResourceGroup, id.Name, id.InferenceClusterName, underlying_resource_action) + future, err := mlComputeClient.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.InferenceClusterName, underlying_resource_action) if err != nil { - return fmt.Errorf("error deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.InferenceClusterName, id.Name, id.ResourceGroup, err) + return fmt.Errorf("error deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", + id.InferenceClusterName, id.WorkspaceName, id.ResourceGroup, err) } - if err := future.WaitForCompletionRef(ctx, machine_learning_compute_client.Client); err != nil { - return fmt.Errorf("error waiting for deletion of Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.InferenceClusterName, id.Name, id.ResourceGroup, err) + if err := future.WaitForCompletionRef(ctx, mlComputeClient.Client); err != nil { + return fmt.Errorf("error waiting for deletion of Inference Cluster %q in workspace %q (Resource Group %q): %+v", + id.InferenceClusterName, id.WorkspaceName, id.ResourceGroup, err) } return nil } func expandSSLConfig(d *schema.ResourceData) *machinelearningservices.SslConfiguration { - // Documentation: https://docs.microsoft.com/en-us/azure/machine-learning/how-to-secure-web-service - var map_ssl_enabled = map[bool]string{ - true: "Enabled", - false: "Disabled", - } - - // --- SSL Configurations --- - ssl_enabled := map_ssl_enabled[d.Get("ssl_enabled").(bool)] - // SSL Certificate default values - cert_file := "" - key_file := "" + ssl_status := "Disabled" + cert := "" + key := "" cname := "" - leaf_domain_label := "" - overwrite_existing_domain := false // SSL Custom Certificate settings - ssl_certificate_custom_refs := d.Get("ssl_certificate_custom").(*schema.Set).List() + ssl := d.Get("ssl").(*schema.Set).List() + + if len(ssl) > 0 && ssl[0] != nil { + ssl_map := ssl[0].(map[string]interface{}) + cert = ssl_map["cert"].(string) + key = ssl_map["key"].(string) + cname = ssl_map["cname"].(string) + } - if len(ssl_certificate_custom_refs) > 0 && ssl_certificate_custom_refs[0] != nil { - ssl_certificate_custom_ref := ssl_certificate_custom_refs[0].(map[string]interface{}) - cert_file = ssl_certificate_custom_ref["cert"].(string) - key_file = ssl_certificate_custom_ref["key"].(string) - cname = ssl_certificate_custom_ref["cname"].(string) + if !(cert == "" && key == "" && cname == "") { + ssl_status = "Enabled" } + // Microsoft Certs do not work unfortunately, so just default values for now + leaf_domain_label := "" + overwrite_existing_domain := false + return &machinelearningservices.SslConfiguration{ - Status: machinelearningservices.Status1(ssl_enabled), - Cert: utils.String(cert_file), - Key: utils.String(key_file), + Status: machinelearningservices.Status1(ssl_status), + Cert: utils.String(cert), + Key: utils.String(key), Cname: utils.String(cname), LeafDomainLabel: utils.String(leaf_domain_label), OverwriteExistingDomain: utils.Bool(overwrite_existing_domain)} } -func flattenCustomSSLConfig(ssl_config *machinelearningservices.SslConfiguration) []interface{} { - return []interface{}{ - map[string]interface{}{ - "cert": *(ssl_config.Cert), - "key": *(ssl_config.Key), - "cname": *(ssl_config.Cname), - }, - } -} - func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node_pool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID //d.Get("subnet_id").(string) service_cidr := *(aks.NetworkProfile.ServiceCidr) @@ -404,7 +415,7 @@ func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node } func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool *containerservice.AgentPool, - ssl_config *machinelearningservices.SslConfiguration, cluster_purpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { + ssl *machinelearningservices.SslConfiguration, cluster_purpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { // https://github.com/Azure/azure-sdk-for-go/blob/v53.1.0/services/containerservice/mgmt/2020-12-01/containerservice/models.go#L1865 fqdn := *(aks_cluster.ManagedClusterProperties.Fqdn) agent_count := *(node_pool.ManagedClusterAgentPoolProfileProperties).Count @@ -425,7 +436,7 @@ func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool // AgentVMSize - Agent virtual machine size AgentVMSize: utils.String(agent_vmsize), // SslConfiguration - SSL configuration - SslConfiguration: ssl_config, + SslConfiguration: ssl, // AksNetworkingConfiguration - AKS networking configuration for vnet AksNetworkingConfiguration: expandAksNetworkingConfiguration(aks_cluster, node_pool), // ClusterPurpose - Possible values include: 'FastProd', 'DenseProd', 'DevTest' @@ -455,20 +466,16 @@ func expandAksComputeProperties(aks_properties *machinelearningservices.AKSPrope } } -func expandMachineLearningComputeClusterIdentity(input []interface{}) *machinelearningservices.Identity { +func expandAksInferenceClusterIdentity(input []interface{}) *machinelearningservices.Identity { if len(input) == 0 { return nil } v := input[0].(map[string]interface{}) - identityType := machinelearningservices.ResourceIdentityType(v["type"].(string)) - - identity := machinelearningservices.Identity{ - Type: identityType, + return &machinelearningservices.Identity{ + Type: machinelearningservices.ResourceIdentityType(v["type"].(string)), } - - return &identity } func flattenAksInferenceClusterIdentity(identity *machinelearningservices.Identity) []interface{} { diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 29d383e4d423..1e6d91972acb 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -31,7 +31,7 @@ func TestAccInferenceCluster_basic(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl_enabled", "cluster_purpose", "description"), + data.ImportStep("cluster_purpose", "description"), }) } @@ -54,13 +54,13 @@ func TestAccInferenceCluster_requiresImport(t *testing.T) { }) } -func TestAccInferenceCluster_custom_ssl_complete(t *testing.T) { +func TestAccInferenceCluster_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") r := InferenceClusterResource{} data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.custom_ssl_config_complete(data), + Config: r.complete(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("identity.#").HasValue("1"), @@ -69,11 +69,10 @@ func TestAccInferenceCluster_custom_ssl_complete(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl_certificate_custom", "ssl_enabled", "cluster_purpose", "description"), + data.ImportStep("ssl", "cluster_purpose", "description"), }) } -/* func TestAccInferenceCluster_basicUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") r := InferenceClusterResource{} @@ -89,7 +88,7 @@ func TestAccInferenceCluster_basicUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl_enabled", "cluster_purpose", "description"), + data.ImportStep("cluster_purpose", "description"), { Config: r.basicUpdate(data), Check: resource.ComposeTestCheckFunc( @@ -100,19 +99,17 @@ func TestAccInferenceCluster_basicUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl_enabled", "cluster_purpose", "description"), + data.ImportStep("cluster_purpose", "description"), }) } -*/ -/* func TestAccInferenceCluster_completeUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") r := InferenceClusterResource{} data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.custom_ssl_config_complete(data), + Config: r.complete(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("identity.#").HasValue("1"), @@ -121,9 +118,9 @@ func TestAccInferenceCluster_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl_certificate_custom", "ssl_enabled", "cluster_purpose", "description"), + data.ImportStep("ssl", "cluster_purpose", "description"), { - Config: r.custom_ssl_config_completeUpdate(data), + Config: r.completeUpdate(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), check.That(data.ResourceName).Key("identity.#").HasValue("1"), @@ -132,10 +129,9 @@ func TestAccInferenceCluster_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl_certificate_custom", "ssl_enabled", "cluster_purpose", "description"), + data.ImportStep("ssl", "cluster_purpose", "description"), }) } -*/ func (r InferenceClusterResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { inferenceClusterClient := client.MachineLearning.MachineLearningComputeClient @@ -144,7 +140,7 @@ func (r InferenceClusterResource) Exists(ctx context.Context, client *clients.Cl return nil, err } - resp, err := inferenceClusterClient.Get(ctx, id.ResourceGroup, id.Name, id.InferenceClusterName) + resp, err := inferenceClusterClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.InferenceClusterName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return utils.Bool(false), nil @@ -161,14 +157,12 @@ func (r InferenceClusterResource) basic(data acceptance.TestData) string { %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" - resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name - workspace_name = azurerm_machine_learning_workspace.test.name - location = azurerm_resource_group.test.location - kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name - kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name - cluster_purpose = "Dev" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + name = "AIC-%d" + machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "Dev" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name identity { type = "SystemAssigned" @@ -183,14 +177,12 @@ func (r InferenceClusterResource) basicUpdate(data acceptance.TestData) string { %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" - resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name - workspace_name = azurerm_machine_learning_workspace.test.name - location = azurerm_resource_group.test.location - kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name - kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name - cluster_purpose = "DevTest" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + name = "AIC-%d" + machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "Dev" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name identity { type = "SystemAssigned" @@ -203,24 +195,21 @@ resource "azurerm_machine_learning_inference_cluster" "test" { `, template, data.RandomIntOfLength(8)) } -func (r InferenceClusterResource) custom_ssl_config_complete(data acceptance.TestData) string { +func (r InferenceClusterResource) complete(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" - resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name - workspace_name = azurerm_machine_learning_workspace.test.name - location = azurerm_resource_group.test.location - kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name - kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name - cluster_purpose = "Test" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name - ssl_enabled = true - ssl_certificate_custom { - cert = file("ssl_config/cert.pem") - key = file("ssl_config/key.pem") + name = "AIC-%d" + machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "Test" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + ssl { + cert = file("testdata/cert.pem") + key = file("testdata/key.pem") cname = "www.contoso.com" } @@ -232,24 +221,21 @@ resource "azurerm_machine_learning_inference_cluster" "test" { `, template, data.RandomIntOfLength(8)) } -func (r InferenceClusterResource) custom_ssl_config_completeUpdate(data acceptance.TestData) string { +func (r InferenceClusterResource) completeUpdate(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" - resource_group_name = azurerm_machine_learning_workspace.test.resource_group_name - workspace_name = azurerm_machine_learning_workspace.test.name - location = azurerm_resource_group.test.location - kubernetes_cluster_name = azurerm_kubernetes_cluster.test.name - kubernetes_cluster_rg = azurerm_kubernetes_cluster.test.resource_group_name - cluster_purpose = "Test" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name - ssl_enabled = true - ssl_certificate_custom { - cert = file("ssl_config/cert.pem") - key = file("ssl_config/key.pem") + name = "AIC-%d" + machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "Test" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + ssl { + cert = file("testdata/cert.pem") + key = file("testdata/key.pem") cname = "www.contoso.com" } @@ -271,14 +257,12 @@ func (r InferenceClusterResource) requiresImport(data acceptance.TestData) strin %s resource "azurerm_machine_learning_inference_cluster" "import" { - name = azurerm_machine_learning_inference_cluster.test.name - resource_group_name = azurerm_machine_learning_inference_cluster.test.resource_group_name - workspace_name = azurerm_machine_learning_inference_cluster.test.workspace_name - location = azurerm_machine_learning_inference_cluster.test.location - kubernetes_cluster_name = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_name - kubernetes_cluster_rg = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_rg - node_pool_name = azurerm_machine_learning_inference_cluster.test.node_pool_name - cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose + name = azurerm_machine_learning_inference_cluster.test.name + machine_learning_workspace_id = azurerm_machine_learning_inference_cluster.test.machine_learning_workspace_id + location = azurerm_machine_learning_inference_cluster.test.location + kubernetes_cluster_id = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_id + node_pool_name = azurerm_machine_learning_inference_cluster.test.node_pool_name + cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose identity { type = "SystemAssigned" @@ -300,6 +284,9 @@ data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "test" { name = "acctestRG-ml-%[1]d" location = "%[2]s" + tags = { + "stage" = "test" + } } resource "azurerm_application_insights" "test" { diff --git a/azurerm/internal/services/machinelearning/parse/inference_cluster.go b/azurerm/internal/services/machinelearning/parse/inference_cluster.go index 0e7222f20d4b..d2962ad1ff6a 100644 --- a/azurerm/internal/services/machinelearning/parse/inference_cluster.go +++ b/azurerm/internal/services/machinelearning/parse/inference_cluster.go @@ -7,7 +7,7 @@ import ( ) type InferenceClusterId struct { - Name string + WorkspaceName string ResourceGroup string InferenceClusterName string } @@ -26,7 +26,7 @@ func InferenceClusterID(input string) (*InferenceClusterId, error) { return nil, err } - if inference_cluster.Name, err = id.PopSegment("workspaces"); err != nil { + if inference_cluster.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } diff --git a/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go b/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go index af664c0d71d8..2367814cbae4 100644 --- a/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go +++ b/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go @@ -49,7 +49,7 @@ func TestInferenceClusterID(t *testing.T) { Error: false, Expect: &InferenceClusterId{ ResourceGroup: "resGroup1", - Name: "workspace1", + WorkspaceName: "workspace1", InferenceClusterName: "cluster1", }, }, @@ -67,8 +67,8 @@ func TestInferenceClusterID(t *testing.T) { t.Fatalf("Expected a value but got an error: %+v", err) } - if actual.Name != v.Expect.Name { - t.Fatalf("Expected %q but got %q for Name", v.Expect.Name, actual.Name) + if actual.WorkspaceName != v.Expect.WorkspaceName { + t.Fatalf("Expected %q but got %q for Workspace Name", v.Expect.WorkspaceName, actual.WorkspaceName) } if actual.ResourceGroup != v.Expect.ResourceGroup { diff --git a/azurerm/internal/services/machinelearning/ssl_config/HOWTO.md b/azurerm/internal/services/machinelearning/testdata/HOWTO.md similarity index 100% rename from azurerm/internal/services/machinelearning/ssl_config/HOWTO.md rename to azurerm/internal/services/machinelearning/testdata/HOWTO.md diff --git a/azurerm/internal/services/machinelearning/ssl_config/cert.pem b/azurerm/internal/services/machinelearning/testdata/cert.pem similarity index 100% rename from azurerm/internal/services/machinelearning/ssl_config/cert.pem rename to azurerm/internal/services/machinelearning/testdata/cert.pem diff --git a/azurerm/internal/services/machinelearning/ssl_config/key.pem b/azurerm/internal/services/machinelearning/testdata/key.pem similarity index 100% rename from azurerm/internal/services/machinelearning/ssl_config/key.pem rename to azurerm/internal/services/machinelearning/testdata/key.pem From 9c92e3b7a9838621b18d0cf808d555ac97e0060a Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Wed, 5 May 2021 20:08:22 +0200 Subject: [PATCH 03/33] Update website docs --- ...e_learning_inference_cluster.html.markdown | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/website/docs/r/machine_learning_inference_cluster.html.markdown b/website/docs/r/machine_learning_inference_cluster.html.markdown index 52a52ae8dff6..4344a4ffdf12 100644 --- a/website/docs/r/machine_learning_inference_cluster.html.markdown +++ b/website/docs/r/machine_learning_inference_cluster.html.markdown @@ -14,17 +14,15 @@ Manages a Machine Learning Inference Cluster. ```hcl resource "azurerm_machine_learning_inference_cluster" "example" { - name = "cluster-name" - resource_group_name = "cluster-rg" - location = "West Europe" - workspace_name = "aml-ws" + name = "example" + location = "West Europe" + kubernetes_cluster_id = "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" identity { - type = "SystemAssigned" + type = "SystemAssigned" } - kubernetes_cluster_name = "k8s-cluster-name" - node_pool_name = "default" - kubernetes_cluster_rg = "k8s-cluster-rg" + machine_learning_workspace_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1" + node_pool_name = "example" } ``` @@ -34,29 +32,23 @@ The following arguments are supported: * `identity` - (Required) A `identity` block as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. -* `kubernetes_cluster_name` - (Required) The name of the Kubernetes Cluster resource to which to attach the inference cluster to. Changing this forces a new Machine Learning Inference Cluster to be created. - -* `kubernetes_cluster_rg` - (Required) The name of the resource group in which the Kubernetes Cluster resides. Changing this forces a new Machine Learning Inference Cluster to be created. +* `kubernetes_cluster_id` - (Required) The ID of the Kubernetes Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. * `location` - (Required) The Azure Region where the Machine Learning Inference Cluster should exist. Changing this forces a new Machine Learning Inference Cluster to be created. +* `machine_learning_workspace_id` - (Required) The ID of the Machine Learning Workspace. Changing this forces a new Machine Learning Inference Cluster to be created. + * `name` - (Required) The name which should be used for this Machine Learning Inference Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. * `node_pool_name` - (Required) The name of the Kubernetes Cluster's node pool. Changing this forces a new Machine Learning Inference Cluster to be created. -* `resource_group_name` - (Required) The name of the Resource Group where the Machine Learning Inference Cluster should exist. Changing this forces a new Machine Learning Inference Cluster to be created. - -* `workspace_name` - (Required) The name of the Azure Machine Learning Workspace where the Machine Learning Inference Cluster should exist. Changing this forces a new Machine Learning Inference Cluster to be created. - --- * `cluster_purpose` - (Optional) The purpose of the Inference Cluster. If used for Development or Testing, use "Dev" or "Test" here. If using for production use "Prod" here. Changing this forces a new Machine Learning Inference Cluster to be created. * `description` - (Optional) The description of the Machine Learning compute. -* `ssl_certificate_custom` - (Optional) One or more `ssl_certificate_custom` blocks as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. - -* `ssl_enabled` - (Optional) Should the SSL Configuration be enabled? +* `ssl` - (Optional) A `ssl` block as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. * `tags` - (Optional) A mapping of tags which should be assigned to the Machine Learning Inference Cluster. @@ -68,13 +60,13 @@ A `identity` block supports the following: --- -A `ssl_certificate_custom` block (maximally *one*) supports the following: +A `ssl` block supports the following: -* `cert` - (Optional) The content of the custom SSL certificate. +* `cert` - (Optional) The certificate for the ssl configuration. -* `cname` - (Optional) The Cname of the custom SSL certificate. +* `cname` - (Optional) The cname of the ssl configuration. -* `key` - (Optional) The content of the key file. +* `key` - (Optional) The key content for the ssl configuration. ## Attributes Reference @@ -82,8 +74,6 @@ In addition to the Arguments listed above - the following Attributes are exporte * `id` - The ID of the Machine Learning Inference Cluster. -* `resource_id` - The ID of the Machine Learning Inference Cluster. - * `sku_name` - The type of SKU. ## Timeouts @@ -101,4 +91,4 @@ Machine Learning Inference Clusters can be imported using the `resource id`, e.g ```shell terraform import azurerm_machine_learning_inference_cluster.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1 -``` \ No newline at end of file +``` From 8206781cb4e4534c8ed5fa47019de0a94ada235a Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Wed, 5 May 2021 21:12:17 +0200 Subject: [PATCH 04/33] tflint (terrafmt -f .) --- ...earning_inference_cluster_resource_test.go | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 1e6d91972acb..244b67eecdcd 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -157,12 +157,12 @@ func (r InferenceClusterResource) basic(data acceptance.TestData) string { %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" + name = "AIC-%d" machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id - location = azurerm_resource_group.test.location - kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "Dev" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "Dev" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name identity { type = "SystemAssigned" @@ -177,19 +177,19 @@ func (r InferenceClusterResource) basicUpdate(data acceptance.TestData) string { %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" + name = "AIC-%d" machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id - location = azurerm_resource_group.test.location - kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "Dev" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "Dev" + node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name identity { type = "SystemAssigned" } tags = { - ENV = "Test" + ENV = "Test" } } `, template, data.RandomIntOfLength(8)) @@ -201,16 +201,16 @@ func (r InferenceClusterResource) complete(data acceptance.TestData) string { %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" + name = "AIC-%d" machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id - location = azurerm_resource_group.test.location - kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "Test" node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name ssl { - cert = file("testdata/cert.pem") - key = file("testdata/key.pem") - cname = "www.contoso.com" + cert = file("testdata/cert.pem") + key = file("testdata/key.pem") + cname = "www.contoso.com" } identity { @@ -227,16 +227,16 @@ func (r InferenceClusterResource) completeUpdate(data acceptance.TestData) strin %s resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" + name = "AIC-%d" machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id - location = azurerm_resource_group.test.location - kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "Test" node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name ssl { - cert = file("testdata/cert.pem") - key = file("testdata/key.pem") - cname = "www.contoso.com" + cert = file("testdata/cert.pem") + key = file("testdata/key.pem") + cname = "www.contoso.com" } identity { @@ -244,7 +244,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { } tags = { - ENV = "Test" + ENV = "Test" } } @@ -257,19 +257,19 @@ func (r InferenceClusterResource) requiresImport(data acceptance.TestData) strin %s resource "azurerm_machine_learning_inference_cluster" "import" { - name = azurerm_machine_learning_inference_cluster.test.name - machine_learning_workspace_id = azurerm_machine_learning_inference_cluster.test.machine_learning_workspace_id - location = azurerm_machine_learning_inference_cluster.test.location - kubernetes_cluster_id = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_id - node_pool_name = azurerm_machine_learning_inference_cluster.test.node_pool_name - cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose - - identity { - type = "SystemAssigned" - } - - tags = azurerm_machine_learning_inference_cluster.test.tags + name = azurerm_machine_learning_inference_cluster.test.name + machine_learning_workspace_id = azurerm_machine_learning_inference_cluster.test.machine_learning_workspace_id + location = azurerm_machine_learning_inference_cluster.test.location + kubernetes_cluster_id = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_id + node_pool_name = azurerm_machine_learning_inference_cluster.test.node_pool_name + cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose + + identity { + type = "SystemAssigned" } + + tags = azurerm_machine_learning_inference_cluster.test.tags +} `, template) } @@ -285,7 +285,7 @@ resource "azurerm_resource_group" "test" { name = "acctestRG-ml-%[1]d" location = "%[2]s" tags = { - "stage" = "test" + "stage" = "test" } } @@ -316,49 +316,49 @@ resource "azurerm_storage_account" "test" { } resource "azurerm_machine_learning_workspace" "test" { - name = "acctest-MLW%[5]d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - application_insights_id = azurerm_application_insights.test.id - key_vault_id = azurerm_key_vault.test.id - storage_account_id = azurerm_storage_account.test.id - - identity { - type = "SystemAssigned" - } + name = "acctest-MLW%[5]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_insights_id = azurerm_application_insights.test.id + key_vault_id = azurerm_key_vault.test.id + storage_account_id = azurerm_storage_account.test.id + + identity { + type = "SystemAssigned" + } } resource "azurerm_virtual_network" "test" { - name = "acctestvirtnet%[6]d" - address_space = ["10.1.0.0/16"] - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name + name = "acctestvirtnet%[6]d" + address_space = ["10.1.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name } - + resource "azurerm_subnet" "test" { - name = "acctestsubnet%[7]d" - resource_group_name = azurerm_resource_group.test.name - virtual_network_name = azurerm_virtual_network.test.name - address_prefix = "10.1.0.0/24" + name = "acctestsubnet%[7]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.1.0.0/24" } resource "azurerm_kubernetes_cluster" "test" { - name = "acctestaks%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - dns_prefix = join("", ["acctestaks", azurerm_resource_group.test.location]) - node_resource_group = "acctestRGAKS-%d" - - default_node_pool { - name = "default" - node_count = 3 - vm_size = "Standard_D11_v2" - vnet_subnet_id = azurerm_subnet.test.id - } - - identity { - type = "SystemAssigned" - } + name = "acctestaks%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + dns_prefix = join("", ["acctestaks", azurerm_resource_group.test.location]) + node_resource_group = "acctestRGAKS-%d" + + default_node_pool { + name = "default" + node_count = 3 + vm_size = "Standard_D11_v2" + vnet_subnet_id = azurerm_subnet.test.id + } + + identity { + type = "SystemAssigned" + } } `, data.RandomInteger, data.Locations.Primary, data.RandomIntOfLength(12), data.RandomIntOfLength(15), data.RandomIntOfLength(16), From 58dc726055a2551ad5bca90e4cd59872185f1991 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Wed, 5 May 2021 21:38:03 +0200 Subject: [PATCH 05/33] Fix golint errors (unused argument and comment formatting) --- .../machine_learning_inference_cluster_resource.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 9ada7f59d105..c444bc1ea576 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -226,7 +226,7 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf aks_properties := expandAksProperties(&aks_cluster, &node_pool, ssl, aks_cluster_purpose) location := azure.NormalizeLocation(d.Get("location").(string)) description := d.Get("description").(string) - aks_compute_properties := expandAksComputeProperties(aks_properties, &aks_cluster, &node_pool, location, description) + aks_compute_properties := expandAksComputeProperties(aks_properties, &aks_cluster, location, description) compute_properties, is_aks := (machinelearningservices.BasicCompute).AsAKS(aks_compute_properties) if !is_aks { return fmt.Errorf("error: No AKS cluster") @@ -402,7 +402,7 @@ func expandSSLConfig(d *schema.ResourceData) *machinelearningservices.SslConfigu } func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node_pool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { - subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID //d.Get("subnet_id").(string) + subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID service_cidr := *(aks.NetworkProfile.ServiceCidr) dns_service_ip := *(aks.NetworkProfile.DNSServiceIP) docker_bridge_cidr := *(aks.NetworkProfile.DockerBridgeCidr) @@ -416,7 +416,6 @@ func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool *containerservice.AgentPool, ssl *machinelearningservices.SslConfiguration, cluster_purpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { - // https://github.com/Azure/azure-sdk-for-go/blob/v53.1.0/services/containerservice/mgmt/2020-12-01/containerservice/models.go#L1865 fqdn := *(aks_cluster.ManagedClusterProperties.Fqdn) agent_count := *(node_pool.ManagedClusterAgentPoolProfileProperties).Count agent_vmsize := string(node_pool.ManagedClusterAgentPoolProfileProperties.VMSize) @@ -444,8 +443,7 @@ func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool } } -func expandAksComputeProperties(aks_properties *machinelearningservices.AKSProperties, aks_cluster *containerservice.ManagedCluster, - node_pool *containerservice.AgentPool, location string, description string) machinelearningservices.AKS { +func expandAksComputeProperties(aks_properties *machinelearningservices.AKSProperties, aks_cluster *containerservice.ManagedCluster, location string, description string) machinelearningservices.AKS { return machinelearningservices.AKS{ // Properties - AKS properties From c7685b98aaa1320c76c8c1685e8487e7c8bf1ff0 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Thu, 6 May 2021 22:33:12 +0200 Subject: [PATCH 06/33] Fix tflint issues (S018, S020, R002, gofmt -s, whitespace) --- ...ine_learning_inference_cluster_resource.go | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index c444bc1ea576..6d7808304b2d 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -109,7 +109,7 @@ func resourceAksInferenceCluster() *schema.Resource { }, "ssl": { - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, ForceNew: true, MaxItems: 1, @@ -142,7 +142,6 @@ func resourceAksInferenceCluster() *schema.Resource { "sku_name": { Type: schema.TypeString, Computed: true, - ForceNew: true, }, "tags": tags.Schema(), @@ -213,7 +212,8 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf t := d.Get("tags").(map[string]interface{}) identity := d.Get("identity").([]interface{}) - ssl := expandSSLConfig(d) + ssl_interface := d.Get("ssl").([]interface{}) + ssl := expandSSLConfig(ssl_interface) cluster_purpose := d.Get("cluster_purpose").(string) var map_cluster_purpose = map[string]string{ @@ -304,7 +304,7 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e if err != nil { return err } - d.Set("machine_learning_workspace_id", *ws_resp.ID) + d.Set("machine_learning_workspace_id", ws_resp.ID) // Retrieve AKS Cluster ID aks_resp, err := aksClient.ListByResourceGroup(ctx, id.ResourceGroup) @@ -367,42 +367,31 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) return nil } -func expandSSLConfig(d *schema.ResourceData) *machinelearningservices.SslConfiguration { - // SSL Certificate default values - ssl_status := "Disabled" - cert := "" - key := "" - cname := "" +func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfiguration { + if len(input) == 0 { + return nil + } - // SSL Custom Certificate settings - ssl := d.Get("ssl").(*schema.Set).List() + v := input[0].(map[string]interface{}) - if len(ssl) > 0 && ssl[0] != nil { - ssl_map := ssl[0].(map[string]interface{}) - cert = ssl_map["cert"].(string) - key = ssl_map["key"].(string) - cname = ssl_map["cname"].(string) - } + // SSL Certificate default values + ssl_status := "Disabled" - if !(cert == "" && key == "" && cname == "") { + if !(v["cert"] == "" && v["key"] == "" && v["cname"] == "") { ssl_status = "Enabled" } - // Microsoft Certs do not work unfortunately, so just default values for now - leaf_domain_label := "" - overwrite_existing_domain := false - return &machinelearningservices.SslConfiguration{ Status: machinelearningservices.Status1(ssl_status), - Cert: utils.String(cert), - Key: utils.String(key), - Cname: utils.String(cname), - LeafDomainLabel: utils.String(leaf_domain_label), - OverwriteExistingDomain: utils.Bool(overwrite_existing_domain)} + Cert: utils.String(v["cert"].(string)), + Key: utils.String(v["key"].(string)), + Cname: utils.String(v["cname"].(string)), + LeafDomainLabel: utils.String(v["leaf_domain_label"].(string)), + OverwriteExistingDomain: utils.Bool(v["overwrite_existing_domain"].(bool))} } func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node_pool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { - subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID + subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID service_cidr := *(aks.NetworkProfile.ServiceCidr) dns_service_ip := *(aks.NetworkProfile.DNSServiceIP) docker_bridge_cidr := *(aks.NetworkProfile.DockerBridgeCidr) @@ -444,7 +433,6 @@ func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool } func expandAksComputeProperties(aks_properties *machinelearningservices.AKSProperties, aks_cluster *containerservice.ManagedCluster, location string, description string) machinelearningservices.AKS { - return machinelearningservices.AKS{ // Properties - AKS properties Properties: aks_properties, From 35117bb1f194a59782aa6cbeb4d559192ef5b13c Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Fri, 7 May 2021 10:58:48 +0200 Subject: [PATCH 07/33] make generate (use resourceids.go) --- ...ine_learning_inference_cluster_resource.go | 24 ++-- ...earning_inference_cluster_resource_test.go | 2 +- .../parse/inference_cluster.go | 80 ++++------- .../parse/inference_cluster_test.go | 135 +++++------------- .../parse/kubernetes_cluster.go | 69 +++++++++ .../parse/kubernetes_cluster_test.go | 112 +++++++++++++++ .../services/machinelearning/resourceids.go | 5 + .../validate/inference_cluster_id.go | 23 +++ .../validate/inference_cluster_id_test.go | 88 ++++++++++++ .../validate/inference_cluster_name.go | 72 ---------- .../validate/inference_cluster_name_test.go | 71 --------- .../validate/kubernetes_cluster_id.go | 23 +++ .../validate/kubernetes_cluster_id_test.go | 76 ++++++++++ 13 files changed, 476 insertions(+), 304 deletions(-) create mode 100644 azurerm/internal/services/machinelearning/parse/kubernetes_cluster.go create mode 100644 azurerm/internal/services/machinelearning/parse/kubernetes_cluster_test.go create mode 100644 azurerm/internal/services/machinelearning/resourceids.go create mode 100644 azurerm/internal/services/machinelearning/validate/inference_cluster_id.go create mode 100644 azurerm/internal/services/machinelearning/validate/inference_cluster_id_test.go delete mode 100644 azurerm/internal/services/machinelearning/validate/inference_cluster_name.go delete mode 100644 azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go create mode 100644 azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id.go create mode 100644 azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id_test.go diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 6d7808304b2d..9d007bdffc78 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -50,7 +50,6 @@ func resourceAksInferenceCluster() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validate.InferenceClusterName, }, "machine_learning_workspace_id": { @@ -65,6 +64,7 @@ func resourceAksInferenceCluster() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + ValidateFunc: validate.KubernetesClusterID, }, "cluster_purpose": { @@ -72,14 +72,12 @@ func resourceAksInferenceCluster() *schema.Resource { Optional: true, ForceNew: true, Default: "Dev", - ValidateFunc: validate.ClusterPurpose, }, "node_pool_name": { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validate.NodePoolName, }, "identity": { @@ -268,7 +266,9 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf return fmt.Errorf("cannot read Inference Cluster ID %q in workspace %q (Resource Group %q) ID", name, workspace_name, resource_group_name) } - d.SetId(*resp.ID) + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + id := parse.NewInferenceClusterID(subscriptionId, resource_group_name, workspace_name, name) + d.SetId(id.ID()) return resourceAksInferenceClusterRead(d, meta) } @@ -283,20 +283,20 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e id, err := parse.InferenceClusterID(d.Id()) if err != nil { - return fmt.Errorf("error parsing Inference Cluster ID `%q`: %+v", d.Id(), err) + return err } - d.Set("name", id.InferenceClusterName) + d.Set("name", id.ComputeName) // Check that Inference Cluster Response can be read - resp, err := mlComputeClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.InferenceClusterName) + resp, err := mlComputeClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } return fmt.Errorf("error making Read request on Inference Cluster %q in Workspace %q (Resource Group %q): %+v", - id.InferenceClusterName, id.WorkspaceName, id.ResourceGroup, err) + id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) } // Retrieve Machine Learning Workspace ID @@ -352,17 +352,17 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) defer cancel() id, err := parse.InferenceClusterID(d.Id()) if err != nil { - return fmt.Errorf("error parsing Inference Cluster ID `%q`: %+v", d.Id(), err) + return err } underlying_resource_action := machinelearningservices.Detach - future, err := mlComputeClient.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.InferenceClusterName, underlying_resource_action) + future, err := mlComputeClient.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName, underlying_resource_action) if err != nil { return fmt.Errorf("error deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", - id.InferenceClusterName, id.WorkspaceName, id.ResourceGroup, err) + id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) } if err := future.WaitForCompletionRef(ctx, mlComputeClient.Client); err != nil { return fmt.Errorf("error waiting for deletion of Inference Cluster %q in workspace %q (Resource Group %q): %+v", - id.InferenceClusterName, id.WorkspaceName, id.ResourceGroup, err) + id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) } return nil } diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 244b67eecdcd..460d904038d6 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -140,7 +140,7 @@ func (r InferenceClusterResource) Exists(ctx context.Context, client *clients.Cl return nil, err } - resp, err := inferenceClusterClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.InferenceClusterName) + resp, err := inferenceClusterClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return utils.Bool(false), nil diff --git a/azurerm/internal/services/machinelearning/parse/inference_cluster.go b/azurerm/internal/services/machinelearning/parse/inference_cluster.go index d2962ad1ff6a..588b8301515a 100644 --- a/azurerm/internal/services/machinelearning/parse/inference_cluster.go +++ b/azurerm/internal/services/machinelearning/parse/inference_cluster.go @@ -1,80 +1,53 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( "fmt" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" ) type InferenceClusterId struct { - WorkspaceName string - ResourceGroup string - InferenceClusterName string -} - -func InferenceClusterID(input string) (*InferenceClusterId, error) { - id, err := azure.ParseAzureResourceID(input) - if err != nil { - return nil, err - } - - inference_cluster := InferenceClusterId{ - ResourceGroup: id.ResourceGroup, - } - - if inference_cluster.InferenceClusterName, err = id.PopSegment("computes"); err != nil { - return nil, err - } - - if inference_cluster.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { - return nil, err - } - - fmt.Printf("Debug: id = %q", id) - - if err := id.ValidateNoEmptySegments(input); err != nil { - return nil, err - } - - return &inference_cluster, nil -} - -type KubernetesClusterId struct { - SubscriptionId string - ResourceGroup string - ManagedClusterName string + SubscriptionId string + ResourceGroup string + WorkspaceName string + ComputeName string } -func NewClusterID(subscriptionId, resourceGroup, managedClusterName string) KubernetesClusterId { - return KubernetesClusterId{ - SubscriptionId: subscriptionId, - ResourceGroup: resourceGroup, - ManagedClusterName: managedClusterName, +func NewInferenceClusterID(subscriptionId, resourceGroup, workspaceName, computeName string) InferenceClusterId { + return InferenceClusterId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + WorkspaceName: workspaceName, + ComputeName: computeName, } } -func (id KubernetesClusterId) String() string { +func (id InferenceClusterId) String() string { segments := []string{ - fmt.Sprintf("Managed Cluster Name %q", id.ManagedClusterName), + fmt.Sprintf("Compute Name %q", id.ComputeName), + fmt.Sprintf("Workspace Name %q", id.WorkspaceName), fmt.Sprintf("Resource Group %q", id.ResourceGroup), } segmentsStr := strings.Join(segments, " / ") - return fmt.Sprintf("%s: (%s)", "Cluster", segmentsStr) + return fmt.Sprintf("%s: (%s)", "Inference Cluster", segmentsStr) } -func (id KubernetesClusterId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s" - return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ManagedClusterName) +func (id InferenceClusterId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.MachineLearningServices/workspaces/%s/computes/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.ComputeName) } -// KubernetesClusterId parses a Cluster ID into an KubernetesClusterId struct -func KubernetesClusterID(input string) (*KubernetesClusterId, error) { +// InferenceClusterID parses a InferenceCluster ID into an InferenceClusterId struct +func InferenceClusterID(input string) (*InferenceClusterId, error) { id, err := azure.ParseAzureResourceID(input) if err != nil { return nil, err } - resourceId := KubernetesClusterId{ + resourceId := InferenceClusterId{ SubscriptionId: id.SubscriptionID, ResourceGroup: id.ResourceGroup, } @@ -87,7 +60,10 @@ func KubernetesClusterID(input string) (*KubernetesClusterId, error) { return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") } - if resourceId.ManagedClusterName, err = id.PopSegment("managedClusters"); err != nil { + if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { + return nil, err + } + if resourceId.ComputeName, err = id.PopSegment("computes"); err != nil { return nil, err } diff --git a/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go b/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go index 2367814cbae4..337f5645d5f8 100644 --- a/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go +++ b/azurerm/internal/services/machinelearning/parse/inference_cluster_test.go @@ -1,101 +1,28 @@ package parse +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + import ( "testing" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" ) -func TestInferenceClusterID(t *testing.T) { - testData := []struct { - Name string - Input string - Error bool - Expect *InferenceClusterId - }{ - { - Name: "Empty", - Input: "", - Error: true, - }, - { - Name: "No Resource Groups Segment", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000", - Error: true, - }, - { - Name: "No Resource Groups Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", - Error: true, - }, - { - Name: "Resource Group ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1", - Error: true, - }, - { - Name: "Missing Workspace Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/", - Error: true, - }, - { - Name: "Missing Machine Learning Workspace Value", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1", - Error: true, - }, - { - Name: "Machine Learning Inference Cluster ID", - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1", - Error: false, - Expect: &InferenceClusterId{ - ResourceGroup: "resGroup1", - WorkspaceName: "workspace1", - InferenceClusterName: "cluster1", - }, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Name) +var _ resourceid.Formatter = InferenceClusterId{} - actual, err := InferenceClusterID(v.Input) - if err != nil { - if v.Error { - continue - } - - t.Fatalf("Expected a value but got an error: %+v", err) - } - - if actual.WorkspaceName != v.Expect.WorkspaceName { - t.Fatalf("Expected %q but got %q for Workspace Name", v.Expect.WorkspaceName, actual.WorkspaceName) - } - - if actual.ResourceGroup != v.Expect.ResourceGroup { - t.Fatalf("Expected %q but got %q for Resource Group", v.Expect.ResourceGroup, actual.ResourceGroup) - } - - if actual.InferenceClusterName != v.Expect.InferenceClusterName { - t.Fatalf("Expected %q but got %q for InferenceClusterName", v.Expect.InferenceClusterName, actual.InferenceClusterName) - } - } -} - -var _ resourceid.Formatter = KubernetesClusterId{} - -func TestKubernetesClusterIDFormatter(t *testing.T) { - actual := NewClusterID("12345678-1234-9876-4563-123456789012", "resGroup1", "cluster1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" +func TestInferenceClusterIDFormatter(t *testing.T) { + actual := NewInferenceClusterID("00000000-0000-0000-0000-000000000000", "resGroup1", "workspace1", "cluster1").ID() + expected := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } } -func TestKubernetesClusterID(t *testing.T) { +func TestInferenceClusterID(t *testing.T) { testData := []struct { Input string Error bool - Expected *KubernetesClusterId + Expected *InferenceClusterId }{ { @@ -118,41 +45,54 @@ func TestKubernetesClusterID(t *testing.T) { { // missing ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", Error: true, }, { // missing value for ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + + { + // missing WorkspaceName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/", Error: true, }, { - // missing ManagedClusterName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", + // missing value for WorkspaceName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/", Error: true, }, { - // missing value for ManagedClusterName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", + // missing ComputeName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/", + Error: true, + }, + + { + // missing value for ComputeName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1", - Expected: &KubernetesClusterId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resGroup1", - ManagedClusterName: "cluster1", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1", + Expected: &InferenceClusterId{ + SubscriptionId: "00000000-0000-0000-0000-000000000000", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + ComputeName: "cluster1", }, }, { // upper-cased - Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1", + Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.MACHINELEARNINGSERVICES/WORKSPACES/WORKSPACE1/COMPUTES/CLUSTER1", Error: true, }, } @@ -160,7 +100,7 @@ func TestKubernetesClusterID(t *testing.T) { for _, v := range testData { t.Logf("[DEBUG] Testing %q", v.Input) - actual, err := KubernetesClusterID(v.Input) + actual, err := InferenceClusterID(v.Input) if err != nil { if v.Error { continue @@ -178,8 +118,11 @@ func TestKubernetesClusterID(t *testing.T) { if actual.ResourceGroup != v.Expected.ResourceGroup { t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) } - if actual.ManagedClusterName != v.Expected.ManagedClusterName { - t.Fatalf("Expected %q but got %q for ManagedClusterName", v.Expected.ManagedClusterName, actual.ManagedClusterName) + if actual.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.ComputeName != v.Expected.ComputeName { + t.Fatalf("Expected %q but got %q for ComputeName", v.Expected.ComputeName, actual.ComputeName) } } } diff --git a/azurerm/internal/services/machinelearning/parse/kubernetes_cluster.go b/azurerm/internal/services/machinelearning/parse/kubernetes_cluster.go new file mode 100644 index 000000000000..8f9d1f2b117b --- /dev/null +++ b/azurerm/internal/services/machinelearning/parse/kubernetes_cluster.go @@ -0,0 +1,69 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type KubernetesClusterId struct { + SubscriptionId string + ResourceGroup string + ManagedClusterName string +} + +func NewKubernetesClusterID(subscriptionId, resourceGroup, managedClusterName string) KubernetesClusterId { + return KubernetesClusterId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ManagedClusterName: managedClusterName, + } +} + +func (id KubernetesClusterId) String() string { + segments := []string{ + fmt.Sprintf("Managed Cluster Name %q", id.ManagedClusterName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Kubernetes Cluster", segmentsStr) +} + +func (id KubernetesClusterId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ManagedClusterName) +} + +// KubernetesClusterID parses a KubernetesCluster ID into an KubernetesClusterId struct +func KubernetesClusterID(input string) (*KubernetesClusterId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := KubernetesClusterId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.ManagedClusterName, err = id.PopSegment("managedClusters"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/machinelearning/parse/kubernetes_cluster_test.go b/azurerm/internal/services/machinelearning/parse/kubernetes_cluster_test.go new file mode 100644 index 000000000000..07f756b8bc66 --- /dev/null +++ b/azurerm/internal/services/machinelearning/parse/kubernetes_cluster_test.go @@ -0,0 +1,112 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = KubernetesClusterId{} + +func TestKubernetesClusterIDFormatter(t *testing.T) { + actual := NewKubernetesClusterID("00000000-0000-0000-0000-000000000000", "resGroup1", "cluster1").ID() + expected := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestKubernetesClusterID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *KubernetesClusterId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + + { + // missing ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", + Error: true, + }, + + { + // missing value for ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1", + Expected: &KubernetesClusterId{ + SubscriptionId: "00000000-0000-0000-0000-000000000000", + ResourceGroup: "resGroup1", + ManagedClusterName: "cluster1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := KubernetesClusterID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.ManagedClusterName != v.Expected.ManagedClusterName { + t.Fatalf("Expected %q but got %q for ManagedClusterName", v.Expected.ManagedClusterName, actual.ManagedClusterName) + } + } +} diff --git a/azurerm/internal/services/machinelearning/resourceids.go b/azurerm/internal/services/machinelearning/resourceids.go new file mode 100644 index 000000000000..30a89ffbbc31 --- /dev/null +++ b/azurerm/internal/services/machinelearning/resourceids.go @@ -0,0 +1,5 @@ +package machinelearning + +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=InferenceCluster -id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=KubernetesCluster -id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=NodePool -id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1 diff --git a/azurerm/internal/services/machinelearning/validate/inference_cluster_id.go b/azurerm/internal/services/machinelearning/validate/inference_cluster_id.go new file mode 100644 index 000000000000..6da0e63bd6ac --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/inference_cluster_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/parse" +) + +func InferenceClusterID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.InferenceClusterID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/machinelearning/validate/inference_cluster_id_test.go b/azurerm/internal/services/machinelearning/validate/inference_cluster_id_test.go new file mode 100644 index 000000000000..48e24c4d7ca2 --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/inference_cluster_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestInferenceClusterID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Valid: false, + }, + + { + // missing WorkspaceName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/", + Valid: false, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/", + Valid: false, + }, + + { + // missing ComputeName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/", + Valid: false, + }, + + { + // missing value for ComputeName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.MACHINELEARNINGSERVICES/WORKSPACES/WORKSPACE1/COMPUTES/CLUSTER1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := InferenceClusterID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/azurerm/internal/services/machinelearning/validate/inference_cluster_name.go b/azurerm/internal/services/machinelearning/validate/inference_cluster_name.go deleted file mode 100644 index b46d7c6599cc..000000000000 --- a/azurerm/internal/services/machinelearning/validate/inference_cluster_name.go +++ /dev/null @@ -1,72 +0,0 @@ -package validate - -import ( - "fmt" - "regexp" -) - -func InferenceClusterName(i interface{}, k string) (warnings []string, errors []error) { - v, ok := i.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) - return - } - - // The portal says: The workspace name must be between 1 and 16 characters. The name may only include alphanumeric characters and '-'. - // If you provide invalid name, the rest api will return an error with the following regex. - if matched := regexp.MustCompile(`^[a-zA-Z0-9][\w-]{1,15}$`).Match([]byte(v)); !matched { - errors = append(errors, fmt.Errorf("%s must be between 2 and 16 characters, and may only include alphanumeric characters and '-' character", k)) - } - return -} - -func KubernetesClusterResourceGroupName(i interface{}, k string) (warnings []string, errors []error) { - v, ok := i.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) - return - } - - // Azure portal (not https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules) says: - // name for managed clusters have to be between 1 and 90 characters. The name may only include alphanumeric characters and '-, _'. - // start and end has to be alphanumeric. If you provide invalid name, the rest api will return an error with the following regex. - if matched := regexp.MustCompile(`^[a-zA-Z0-9][\w-_]{1,90}$`).Match([]byte(v)); !matched { - errors = append(errors, fmt.Errorf("%s must be between 1 and 90 characters, and may only include alphanumeric characters and '-, :' character", k)) - } - return -} - -func NodePoolName(i interface{}, k string) (warnings []string, errors []error) { - v, ok := i.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) - return - } - - if matched := regexp.MustCompile(`^[a-zA-Z0-9][\w-_]{1,12}$`).Match([]byte(v)); !matched { - errors = append(errors, fmt.Errorf("%s must be between 1 and 12 characters, and may only include alphanumeric characters and '-, :' character", k)) - } - return -} - -func ClusterPurpose(i interface{}, k string) (warnings []string, errors []error) { - v, ok := i.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) - return - } - - // Azure portal (not https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules) says: - // name for managed clusters have to be between 1 and 90 characters. The name may only include alphanumeric characters and '-, _'. - // start and end has to be alphanumeric. If you provide invalid name, the rest api will return an error with the following regex. - - switch v { - case - "Prod", - "Dev", - "Test": - return - } - errors = append(errors, fmt.Errorf("%s must be one of \"Prod\", \"Dev\", \"Test\" ", k)) - return -} diff --git a/azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go b/azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go deleted file mode 100644 index 74490af4e7da..000000000000 --- a/azurerm/internal/services/machinelearning/validate/inference_cluster_name_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package validate - -import "testing" - -func TestInferenceClusterName(t *testing.T) { - testData := []struct { - input string - expected bool - }{ - { - // empty - input: "", - expected: false, - }, - { - // basic example - input: "hello", - expected: true, - }, - { - // cannot start with a hyphen - input: "-hello", - expected: false, - }, - { - // can end with a hyphen - input: "hello-", - expected: true, - }, - { - // cannot contain other special symbols other than hyphens - input: "hello.world", - expected: false, - }, - { - // hyphen in the middle - input: "hello-world", - expected: true, - }, - { - // 1 char - input: "a", - expected: false, - }, - { - // 2 chars - input: "ab", - expected: true, - }, - { - // 16 chars - input: "abcdefghijklmnop", - expected: true, - }, - { - // 17 chars - input: "abcdefghijklmnopq", - expected: false, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.input) - - _, errors := InferenceClusterName(v.input, "name") - actual := len(errors) == 0 - if v.expected != actual { - t.Fatalf("Expected %t but got %t", v.expected, actual) - } - } -} diff --git a/azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id.go b/azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id.go new file mode 100644 index 000000000000..6eab63174bd3 --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/parse" +) + +func KubernetesClusterID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.KubernetesClusterID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id_test.go b/azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id_test.go new file mode 100644 index 000000000000..29c4c30f29ab --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/kubernetes_cluster_id_test.go @@ -0,0 +1,76 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestKubernetesClusterID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Valid: false, + }, + + { + // missing ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", + Valid: false, + }, + + { + // missing value for ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := KubernetesClusterID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} From 09d96fefd1c63612b93d233a746c7664009dfb96 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Fri, 7 May 2021 12:30:11 +0200 Subject: [PATCH 08/33] Use node pool id and auto-generated node pool validators and parsers --- ...ine_learning_inference_cluster_resource.go | 37 +++-- ...earning_inference_cluster_resource_test.go | 10 +- .../machinelearning/parse/node_pool.go | 75 ++++++++++ .../machinelearning/parse/node_pool_test.go | 128 ++++++++++++++++++ .../machinelearning/validate/node_pool_id.go | 23 ++++ .../validate/node_pool_id_test.go | 88 ++++++++++++ ...e_learning_inference_cluster.html.markdown | 6 +- 7 files changed, 346 insertions(+), 21 deletions(-) create mode 100644 azurerm/internal/services/machinelearning/parse/node_pool.go create mode 100644 azurerm/internal/services/machinelearning/parse/node_pool_test.go create mode 100644 azurerm/internal/services/machinelearning/validate/node_pool_id.go create mode 100644 azurerm/internal/services/machinelearning/validate/node_pool_id_test.go diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 9d007bdffc78..bb36f79babd0 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -47,9 +47,9 @@ func resourceAksInferenceCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "machine_learning_workspace_id": { @@ -61,23 +61,24 @@ func resourceAksInferenceCluster() *schema.Resource { "location": azure.SchemaLocation(), "kubernetes_cluster_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, ValidateFunc: validate.KubernetesClusterID, }, "cluster_purpose": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "Dev", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "Dev", }, - "node_pool_name": { + "node_pool_id": { Type: schema.TypeString, Required: true, ForceNew: true, + ValidateFunc: validate.NodePoolID, }, "identity": { @@ -201,7 +202,12 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf return err } - pool_name := d.Get("node_pool_name").(string) + pool_id := d.Get("node_pool_id").(string) + pool_id_details, err := parse.NodePoolID(pool_id) + if err != nil { + return err + } + pool_name := pool_id_details.AgentPoolName node_pool, err := poolsClient.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name, pool_name) if err != nil { return err @@ -322,7 +328,12 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e kubernetes_cluster_name := aks_id_details.ManagedClusterName node_pool_list, _ := poolsClient.List(ctx, id.ResourceGroup, kubernetes_cluster_name) - pool_name := node_pool_list.Values()[0].Name + pool_id := *(node_pool_list.Values()[0].ID) + pool_id_details, err := parse.NodePoolID(pool_id) + if err != nil { + return err + } + pool_name := pool_id_details.AgentPoolName d.Set("kubernetes_cluster_id", aks_id) d.Set("node_pool_name", pool_name) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 460d904038d6..764219988d6b 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -162,7 +162,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "Dev" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id identity { type = "SystemAssigned" @@ -182,7 +182,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "Dev" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id identity { type = "SystemAssigned" @@ -206,7 +206,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "Test" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id ssl { cert = file("testdata/cert.pem") key = file("testdata/key.pem") @@ -232,7 +232,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "Test" - node_pool_name = azurerm_kubernetes_cluster.test.default_node_pool[0].name + node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id ssl { cert = file("testdata/cert.pem") key = file("testdata/key.pem") @@ -261,7 +261,7 @@ resource "azurerm_machine_learning_inference_cluster" "import" { machine_learning_workspace_id = azurerm_machine_learning_inference_cluster.test.machine_learning_workspace_id location = azurerm_machine_learning_inference_cluster.test.location kubernetes_cluster_id = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_id - node_pool_name = azurerm_machine_learning_inference_cluster.test.node_pool_name + node_pool_id = azurerm_machine_learning_inference_cluster.test.node_pool_id cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose identity { diff --git a/azurerm/internal/services/machinelearning/parse/node_pool.go b/azurerm/internal/services/machinelearning/parse/node_pool.go new file mode 100644 index 000000000000..04f0930ed519 --- /dev/null +++ b/azurerm/internal/services/machinelearning/parse/node_pool.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type NodePoolId struct { + SubscriptionId string + ResourceGroup string + ManagedClusterName string + AgentPoolName string +} + +func NewNodePoolID(subscriptionId, resourceGroup, managedClusterName, agentPoolName string) NodePoolId { + return NodePoolId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ManagedClusterName: managedClusterName, + AgentPoolName: agentPoolName, + } +} + +func (id NodePoolId) String() string { + segments := []string{ + fmt.Sprintf("Agent Pool Name %q", id.AgentPoolName), + fmt.Sprintf("Managed Cluster Name %q", id.ManagedClusterName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Node Pool", segmentsStr) +} + +func (id NodePoolId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s/agentPools/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ManagedClusterName, id.AgentPoolName) +} + +// NodePoolID parses a NodePool ID into an NodePoolId struct +func NodePoolID(input string) (*NodePoolId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := NodePoolId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.ManagedClusterName, err = id.PopSegment("managedClusters"); err != nil { + return nil, err + } + if resourceId.AgentPoolName, err = id.PopSegment("agentPools"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/machinelearning/parse/node_pool_test.go b/azurerm/internal/services/machinelearning/parse/node_pool_test.go new file mode 100644 index 000000000000..73f280445fa4 --- /dev/null +++ b/azurerm/internal/services/machinelearning/parse/node_pool_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = NodePoolId{} + +func TestNodePoolIDFormatter(t *testing.T) { + actual := NewNodePoolID("00000000-0000-0000-0000-000000000000", "resGroup1", "cluster1", "pool1").ID() + expected := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestNodePoolID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *NodePoolId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Error: true, + }, + + { + // missing ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", + Error: true, + }, + + { + // missing value for ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", + Error: true, + }, + + { + // missing AgentPoolName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/", + Error: true, + }, + + { + // missing value for AgentPoolName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1", + Expected: &NodePoolId{ + SubscriptionId: "00000000-0000-0000-0000-000000000000", + ResourceGroup: "resGroup1", + ManagedClusterName: "cluster1", + AgentPoolName: "pool1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1/AGENTPOOLS/POOL1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := NodePoolID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.ManagedClusterName != v.Expected.ManagedClusterName { + t.Fatalf("Expected %q but got %q for ManagedClusterName", v.Expected.ManagedClusterName, actual.ManagedClusterName) + } + if actual.AgentPoolName != v.Expected.AgentPoolName { + t.Fatalf("Expected %q but got %q for AgentPoolName", v.Expected.AgentPoolName, actual.AgentPoolName) + } + } +} diff --git a/azurerm/internal/services/machinelearning/validate/node_pool_id.go b/azurerm/internal/services/machinelearning/validate/node_pool_id.go new file mode 100644 index 000000000000..10d41b9364a1 --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/node_pool_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/parse" +) + +func NodePoolID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.NodePoolID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/machinelearning/validate/node_pool_id_test.go b/azurerm/internal/services/machinelearning/validate/node_pool_id_test.go new file mode 100644 index 000000000000..f9b3216e64b9 --- /dev/null +++ b/azurerm/internal/services/machinelearning/validate/node_pool_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestNodePoolID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Valid: false, + }, + + { + // missing ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", + Valid: false, + }, + + { + // missing value for ManagedClusterName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", + Valid: false, + }, + + { + // missing AgentPoolName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/", + Valid: false, + }, + + { + // missing value for AgentPoolName + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1/AGENTPOOLS/POOL1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := NodePoolID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/machine_learning_inference_cluster.html.markdown b/website/docs/r/machine_learning_inference_cluster.html.markdown index 4344a4ffdf12..cd4bf4ff5c32 100644 --- a/website/docs/r/machine_learning_inference_cluster.html.markdown +++ b/website/docs/r/machine_learning_inference_cluster.html.markdown @@ -16,13 +16,13 @@ Manages a Machine Learning Inference Cluster. resource "azurerm_machine_learning_inference_cluster" "example" { name = "example" location = "West Europe" - kubernetes_cluster_id = "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" + kubernetes_cluster_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" identity { type = "SystemAssigned" } machine_learning_workspace_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1" - node_pool_name = "example" + node_pool_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1" } ``` @@ -40,7 +40,7 @@ The following arguments are supported: * `name` - (Required) The name which should be used for this Machine Learning Inference Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. -* `node_pool_name` - (Required) The name of the Kubernetes Cluster's node pool. Changing this forces a new Machine Learning Inference Cluster to be created. +* `node_pool_id` - (Required) The ID of the Kubernetes Cluster's node pool. Changing this forces a new Machine Learning Inference Cluster to be created. --- From 8c3f213182ada99cb58b95f8451b6bc0199542e4 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Fri, 7 May 2021 14:55:04 +0200 Subject: [PATCH 09/33] update docs (website lint) --- website/docs/r/machine_learning_inference_cluster.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/machine_learning_inference_cluster.html.markdown b/website/docs/r/machine_learning_inference_cluster.html.markdown index cd4bf4ff5c32..821963cf69ee 100644 --- a/website/docs/r/machine_learning_inference_cluster.html.markdown +++ b/website/docs/r/machine_learning_inference_cluster.html.markdown @@ -22,7 +22,7 @@ resource "azurerm_machine_learning_inference_cluster" "example" { type = "SystemAssigned" } machine_learning_workspace_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1" - node_pool_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1" + node_pool_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1" } ``` From 5293cf2ca8f9d6b96307c47939a5f54608566d9c Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sat, 8 May 2021 23:18:13 +0200 Subject: [PATCH 10/33] use camel case for all variables --- ...ine_learning_inference_cluster_resource.go | 131 +++++++++--------- 1 file changed, 62 insertions(+), 69 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index bb36f79babd0..d7ecd3df5f4f 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -24,7 +24,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -const min_number_of_nodes_prod int32 = 12 +const minNumberOfNodesProd int32 = 12 func resourceAksInferenceCluster() *schema.Resource { return &schema.Resource{ @@ -160,17 +160,15 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf name := d.Get("name").(string) // Get Machine Learning Workspace Name and Resource Group from ID - ml_workspace_id := d.Get("machine_learning_workspace_id").(string) - ws_id, err := parse.WorkspaceID(ml_workspace_id) + unparsedWorkspaceID := d.Get("machine_learning_workspace_id").(string) + + workspaceID, err := parse.WorkspaceID(unparsedWorkspaceID) if err != nil { return err } - workspace_name := ws_id.Name - resource_group_name := ws_id.ResourceGroup - // Check if Inference Cluster already exists - existing, err := mlComputeClient.Get(ctx, resource_group_name, workspace_name, name) + existing, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { return fmt.Errorf("error checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspace_name, resource_group_name, err) @@ -181,34 +179,31 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf } // Get SKU from Workspace - aml_ws, err := mlWorkspacesClient.Get(ctx, resource_group_name, workspace_name) + workspace, err := mlWorkspacesClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name) if err != nil { return err } - sku := aml_ws.Sku + sku := workspace.Sku // Get Kubernetes Cluster Name and Resource Group from ID - aks_id := d.Get("kubernetes_cluster_id").(string) - aks_id_details, err := parse.KubernetesClusterID(aks_id) + unparsedAksID := d.Get("kubernetes_cluster_id").(string) + aksID, err := parse.KubernetesClusterID(unparsedAksID) if err != nil { return err } - kubernetes_cluster_name := aks_id_details.ManagedClusterName - kubernetes_cluster_rg := aks_id_details.ResourceGroup // Get Existing AKS - aks_cluster, err := aksClient.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name) + aks, err := aksClient.Get(ctx, aksID.ResourceGroup, aksID.ManagedClusterName) if err != nil { return err } - pool_id := d.Get("node_pool_id").(string) - pool_id_details, err := parse.NodePoolID(pool_id) + unparsedAgentPoolID := d.Get("node_pool_id").(string) + agentPoolID, err := parse.NodePoolID(unparsedAgentPoolID ) if err != nil { return err } - pool_name := pool_id_details.AgentPoolName - node_pool, err := poolsClient.Get(ctx, kubernetes_cluster_rg, kubernetes_cluster_name, pool_name) + nodePool, err := poolsClient.Get(ctx, aksID.ResourceGroup, aksID.ManagedClusterName, agentPoolID.AgentPoolName) if err != nil { return err } @@ -216,29 +211,29 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf t := d.Get("tags").(map[string]interface{}) identity := d.Get("identity").([]interface{}) - ssl_interface := d.Get("ssl").([]interface{}) - ssl := expandSSLConfig(ssl_interface) + sslInterface := d.Get("ssl").([]interface{}) + ssl := expandSSLConfig(sslInterface) - cluster_purpose := d.Get("cluster_purpose").(string) - var map_cluster_purpose = map[string]string{ + unmappedclusterPurpose := d.Get("cluster_purpose").(string) + var mapClusterPurpose = map[string]string{ "Dev": "DevTest", "Test": "DevTest", "Prod": "FastProd", } - aks_cluster_purpose := machinelearningservices.ClusterPurpose(map_cluster_purpose[cluster_purpose]) + clusterPurpose := machinelearningservices.ClusterPurpose(mapClusterPurpose[unmappedclusterPurpose]) - aks_properties := expandAksProperties(&aks_cluster, &node_pool, ssl, aks_cluster_purpose) + aksProperties := expandAksProperties(&aks, &nodePool, ssl, clusterPurpose) location := azure.NormalizeLocation(d.Get("location").(string)) description := d.Get("description").(string) - aks_compute_properties := expandAksComputeProperties(aks_properties, &aks_cluster, location, description) - compute_properties, is_aks := (machinelearningservices.BasicCompute).AsAKS(aks_compute_properties) - if !is_aks { + aksComputeProperties := expandAksComputeProperties(aksProperties, &aks, location, description) + aksCompute, isAks := (machinelearningservices.BasicCompute).AsAKS(aksComputeProperties) + if !isAks { return fmt.Errorf("error: No AKS cluster") } - inference_cluster_parameters := machinelearningservices.ComputeResource{ + inferenceClusterParameters := machinelearningservices.ComputeResource{ // Properties - Compute properties - Properties: compute_properties, + Properties: aksCompute, // ID - READ-ONLY; Specifies the resource ID. // Name - READ-ONLY; Specifies the name of the resource. // Identity - The identity of the resource. @@ -253,27 +248,27 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf } if v, ok := d.GetOk("description"); ok { - aks_compute_properties.Description = utils.String(v.(string)) + aksCompute.Description = utils.String(v.(string)) } - future, err := mlComputeClient.CreateOrUpdate(ctx, resource_group_name, workspace_name, name, inference_cluster_parameters) + future, err := mlComputeClient.CreateOrUpdate(ctx, workspaceID.ResourceGroup, workspaceID.Name, name, inferenceClusterParameters) if err != nil { - return fmt.Errorf("error creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) + return fmt.Errorf("error creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) } if err := future.WaitForCompletionRef(ctx, mlComputeClient.Client); err != nil { - return fmt.Errorf("error waiting for creation of Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) + return fmt.Errorf("error waiting for creation of Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) } - resp, err := mlComputeClient.Get(ctx, resource_group_name, workspace_name, name) + resp, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) if err != nil { - return fmt.Errorf("error retrieving Inference Cluster Compute %q in workspace %q (Resource Group %q): %+v", name, workspace_name, resource_group_name, err) + return fmt.Errorf("error retrieving Inference Cluster Compute %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) } if resp.ID == nil { - return fmt.Errorf("cannot read Inference Cluster ID %q in workspace %q (Resource Group %q) ID", name, workspace_name, resource_group_name) + return fmt.Errorf("cannot read Inference Cluster ID %q in workspace %q (Resource Group %q) ID", name, workspaceID.Name, workspaceID.ResourceGroup) } subscriptionId := meta.(*clients.Client).Account.SubscriptionId - id := parse.NewInferenceClusterID(subscriptionId, resource_group_name, workspace_name, name) + id := parse.NewInferenceClusterID(subscriptionId, workspaceID.ResourceGroup, workspaceID.Name, name) d.SetId(id.ID()) return resourceAksInferenceClusterRead(d, meta) @@ -306,37 +301,35 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e } // Retrieve Machine Learning Workspace ID - ws_resp, err := mlWorkspacesClient.Get(ctx, id.ResourceGroup, id.WorkspaceName) + mlResp, err := mlWorkspacesClient.Get(ctx, id.ResourceGroup, id.WorkspaceName) if err != nil { return err } - d.Set("machine_learning_workspace_id", ws_resp.ID) + d.Set("machine_learning_workspace_id", mlResp.ID) // Retrieve AKS Cluster ID - aks_resp, err := aksClient.ListByResourceGroup(ctx, id.ResourceGroup) + aksResp, err := aksClient.ListByResourceGroup(ctx, id.ResourceGroup) if err != nil { return err } - aks_id := *(aks_resp.Values()[0].ID) + unparsedAksId := *(aksResp.Values()[0].ID) // Retrieve AKS Cluster name and Node pool name from ID - aks_id_details, err := parse.KubernetesClusterID(aks_id) + aksId, err := parse.KubernetesClusterID(unparsedAksId) if err != nil { return err } - kubernetes_cluster_name := aks_id_details.ManagedClusterName - node_pool_list, _ := poolsClient.List(ctx, id.ResourceGroup, kubernetes_cluster_name) - pool_id := *(node_pool_list.Values()[0].ID) - pool_id_details, err := parse.NodePoolID(pool_id) + nodePoolList, _ := poolsClient.List(ctx, id.ResourceGroup, aksId.ManagedClusterName) + unparsedPoolId := *(nodePoolList.Values()[0].ID) + poolId, err := parse.NodePoolID(unparsedPoolId) if err != nil { return err } - pool_name := pool_id_details.AgentPoolName - d.Set("kubernetes_cluster_id", aks_id) - d.Set("node_pool_name", pool_name) + d.Set("kubernetes_cluster_id", aksId) + d.Set("node_pool_name", poolId.AgentPoolName) // Retrieve location if location := resp.Location; location != nil { @@ -365,8 +358,8 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) if err != nil { return err } - underlying_resource_action := machinelearningservices.Detach - future, err := mlComputeClient.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName, underlying_resource_action) + + future, err := mlComputeClient.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName, machinelearningservices.Detach) if err != nil { return fmt.Errorf("error deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) @@ -386,14 +379,14 @@ func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfigurat v := input[0].(map[string]interface{}) // SSL Certificate default values - ssl_status := "Disabled" + sslStatus := "Disabled" if !(v["cert"] == "" && v["key"] == "" && v["cname"] == "") { - ssl_status = "Enabled" + sslStatus = "Enabled" } return &machinelearningservices.SslConfiguration{ - Status: machinelearningservices.Status1(ssl_status), + Status: machinelearningservices.Status1(sslStatus), Cert: utils.String(v["cert"].(string)), Key: utils.String(v["key"].(string)), Cname: utils.String(v["cname"].(string)), @@ -414,15 +407,15 @@ func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node DockerBridgeCidr: utils.String(docker_bridge_cidr)} } -func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool *containerservice.AgentPool, - ssl *machinelearningservices.SslConfiguration, cluster_purpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { - fqdn := *(aks_cluster.ManagedClusterProperties.Fqdn) - agent_count := *(node_pool.ManagedClusterAgentPoolProfileProperties).Count - agent_vmsize := string(node_pool.ManagedClusterAgentPoolProfileProperties.VMSize) +func expandAksProperties(aks *containerservice.ManagedCluster, nodePool *containerservice.AgentPool, + ssl *machinelearningservices.SslConfiguration, clusterPurpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { + fqdn := *(aks.ManagedClusterProperties.Fqdn) + agentCount := *(nodePool.ManagedClusterAgentPoolProfileProperties).Count + agentVmSize := string(nodePool.ManagedClusterAgentPoolProfileProperties.VMSize) - if agent_count < min_number_of_nodes_prod && cluster_purpose == "FastProd" { - min_number_of_cores_needed := int(math.Ceil(float64(min_number_of_nodes_prod) / float64(agent_count))) - err := fmt.Errorf("error: you should pick a VM with at least %d cores", min_number_of_cores_needed) + if agentCount < minNumberOfNodesProd && clusterPurpose == "FastProd" { + minNumberOfCores := int(math.Ceil(float64(minNumberOfNodesProd) / float64(agentCount))) + err := fmt.Errorf("error: you should pick a VM with at least %d cores", minNumberOfCores) fmt.Println(err.Error()) } @@ -431,22 +424,22 @@ func expandAksProperties(aks_cluster *containerservice.ManagedCluster, node_pool ClusterFqdn: utils.String(fqdn), // SystemServices - READ-ONLY; System services // AgentCount - Number of agents - AgentCount: utils.Int32(agent_count), + AgentCount: utils.Int32(agentCount), // AgentVMSize - Agent virtual machine size - AgentVMSize: utils.String(agent_vmsize), + AgentVMSize: utils.String(agentVmSize), // SslConfiguration - SSL configuration SslConfiguration: ssl, // AksNetworkingConfiguration - AKS networking configuration for vnet - AksNetworkingConfiguration: expandAksNetworkingConfiguration(aks_cluster, node_pool), + AksNetworkingConfiguration: expandAksNetworkingConfiguration(aks, nodePool), // ClusterPurpose - Possible values include: 'FastProd', 'DenseProd', 'DevTest' - ClusterPurpose: cluster_purpose, + ClusterPurpose: clusterPurpose, } } -func expandAksComputeProperties(aks_properties *machinelearningservices.AKSProperties, aks_cluster *containerservice.ManagedCluster, location string, description string) machinelearningservices.AKS { +func expandAksComputeProperties(aksProperties *machinelearningservices.AKSProperties, aks *containerservice.ManagedCluster, location string, description string) machinelearningservices.AKS { return machinelearningservices.AKS{ // Properties - AKS properties - Properties: aks_properties, + Properties: aksProperties, // ComputeLocation - Location for the underlying compute ComputeLocation: &location, // ProvisioningState - READ-ONLY; The provision state of the cluster. Valid values are Unknown, Updating, Provisioning, Succeeded, and Failed. Possible values include: 'ProvisioningStateUnknown', 'ProvisioningStateUpdating', 'ProvisioningStateCreating', 'ProvisioningStateDeleting', 'ProvisioningStateSucceeded', 'ProvisioningStateFailed', 'ProvisioningStateCanceled' @@ -455,7 +448,7 @@ func expandAksComputeProperties(aks_properties *machinelearningservices.AKSPrope // CreatedOn - READ-ONLY; The date and time when the compute was created. // ModifiedOn - READ-ONLY; The date and time when the compute was last modified. // ResourceID - ARM resource id of the underlying compute - ResourceID: aks_cluster.ID, + ResourceID: aks.ID, // ProvisioningErrors - READ-ONLY; Errors during provisioning // IsAttachedCompute - READ-ONLY; Indicating whether the compute was provisioned by user and brought from outside if true, or machine learning service provisioned it if false. // ComputeType - Possible values include: 'ComputeTypeCompute', 'ComputeTypeAKS1', 'ComputeTypeAmlCompute1', 'ComputeTypeVirtualMachine1', 'ComputeTypeHDInsight1', 'ComputeTypeDataFactory1', 'ComputeTypeDatabricks1', 'ComputeTypeDataLakeAnalytics1' From 48c75a7dbacfacf04ceb0226316f103e8fa1f90e Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sun, 9 May 2021 11:18:22 +0200 Subject: [PATCH 11/33] Fix missing camel cases --- .../machine_learning_inference_cluster_resource.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index d7ecd3df5f4f..ea8f8e35fa35 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -171,7 +171,7 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf existing, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("error checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspace_name, resource_group_name, err) + return fmt.Errorf("error checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspaceID.Name, workspaceID.ResourceGroup, err) } } if existing.ID != nil && *existing.ID != "" { @@ -394,8 +394,8 @@ func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfigurat OverwriteExistingDomain: utils.Bool(v["overwrite_existing_domain"].(bool))} } -func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, node_pool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { - subnet_id := *(node_pool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID +func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, nodePool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { + subnet_id := *(nodePool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID service_cidr := *(aks.NetworkProfile.ServiceCidr) dns_service_ip := *(aks.NetworkProfile.DNSServiceIP) docker_bridge_cidr := *(aks.NetworkProfile.DockerBridgeCidr) From c1d6dbe824f6f87ccd44e8b18bae3305e5436c24 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sun, 9 May 2021 18:47:51 +0200 Subject: [PATCH 12/33] Use ComputeResource to get AKS Cluster ID and other properties --- ...ine_learning_inference_cluster_resource.go | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index ea8f8e35fa35..a28913767977 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -214,13 +214,13 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf sslInterface := d.Get("ssl").([]interface{}) ssl := expandSSLConfig(sslInterface) - unmappedclusterPurpose := d.Get("cluster_purpose").(string) + unmappedClusterPurpose := d.Get("cluster_purpose").(string) var mapClusterPurpose = map[string]string{ "Dev": "DevTest", "Test": "DevTest", "Prod": "FastProd", } - clusterPurpose := machinelearningservices.ClusterPurpose(mapClusterPurpose[unmappedclusterPurpose]) + clusterPurpose := machinelearningservices.ClusterPurpose(mapClusterPurpose[unmappedClusterPurpose]) aksProperties := expandAksProperties(&aks, &nodePool, ssl, clusterPurpose) location := azure.NormalizeLocation(d.Get("location").(string)) @@ -267,8 +267,10 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf return fmt.Errorf("cannot read Inference Cluster ID %q in workspace %q (Resource Group %q) ID", name, workspaceID.Name, workspaceID.ResourceGroup) } - subscriptionId := meta.(*clients.Client).Account.SubscriptionId - id := parse.NewInferenceClusterID(subscriptionId, workspaceID.ResourceGroup, workspaceID.Name, name) + id, err := parse.InferenceClusterID(*resp.ID) + if err != nil { + return err + } d.SetId(id.ID()) return resourceAksInferenceClusterRead(d, meta) @@ -277,7 +279,6 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) error { mlWorkspacesClient := meta.(*clients.Client).MachineLearning.WorkspacesClient mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient - aksClient := meta.(*clients.Client).Containers.KubernetesClustersClient poolsClient := meta.(*clients.Client).Containers.AgentPoolsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -290,9 +291,9 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.Set("name", id.ComputeName) // Check that Inference Cluster Response can be read - resp, err := mlComputeClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName) + computeResource, err := mlComputeClient.Get(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName) if err != nil { - if utils.ResponseWasNotFound(resp.Response) { + if utils.ResponseWasNotFound(computeResource.Response) { d.SetId("") return nil } @@ -307,14 +308,14 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e } d.Set("machine_learning_workspace_id", mlResp.ID) - // Retrieve AKS Cluster ID - aksResp, err := aksClient.ListByResourceGroup(ctx, id.ResourceGroup) - if err != nil { - return err + // use ComputeResource to get to AKS Cluster ID and other properties + aksCompute, isAks := (machinelearningservices.BasicCompute).AsAKS(computeResource.Properties) + if !isAks { + return fmt.Errorf("compute resource %s is not an AKS cluster", id.ComputeName) } - unparsedAksId := *(aksResp.Values()[0].ID) - + unparsedAksId := *aksCompute.ResourceID + // Retrieve AKS Cluster name and Node pool name from ID aksId, err := parse.KubernetesClusterID(unparsedAksId) if err != nil { @@ -332,22 +333,22 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.Set("node_pool_name", poolId.AgentPoolName) // Retrieve location - if location := resp.Location; location != nil { + if location := computeResource.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } // Retrieve Sku - if sku := resp.Sku; sku != nil { + if sku := computeResource.Sku; sku != nil { d.Set("sku_name", sku.Name) } // Retrieve Identity - if err := d.Set("identity", flattenAksInferenceClusterIdentity(resp.Identity)); err != nil { + if err := d.Set("identity", flattenAksInferenceClusterIdentity(computeResource.Identity)); err != nil { return fmt.Errorf("error flattening identity on Workspace %q (Resource Group %q): %+v", id.WorkspaceName, id.ResourceGroup, err) } - return tags.FlattenAndSet(d, resp.Tags) + return tags.FlattenAndSet(d, computeResource.Tags) } func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) error { From c737becbd637933891955dbcb052a1d103193cae Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Mon, 10 May 2021 08:08:16 +0200 Subject: [PATCH 13/33] Infer cluster_purpose, description and ssl config parameters as well -> no ignores in ImportStep anymore --- ...ine_learning_inference_cluster_resource.go | 12 +++++++++-- ...earning_inference_cluster_resource_test.go | 20 +++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index a28913767977..149d0ef43cb5 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -216,8 +216,7 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf unmappedClusterPurpose := d.Get("cluster_purpose").(string) var mapClusterPurpose = map[string]string{ - "Dev": "DevTest", - "Test": "DevTest", + "DevTest": "DevTest", "Prod": "FastProd", } clusterPurpose := machinelearningservices.ClusterPurpose(mapClusterPurpose[unmappedClusterPurpose]) @@ -332,6 +331,15 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.Set("kubernetes_cluster_id", aksId) d.Set("node_pool_name", poolId.AgentPoolName) + var mapClusterPurpose = map[string]string{ + "DevTest": "DevTest", + "FastProd": "Prod", + } + d.Set("cluster_purpose", mapClusterPurpose[string(aksCompute.Properties.ClusterPurpose)]) + + d.Set("description", aksCompute.Description) + d.Set("ssl", aksCompute.Properties.SslConfiguration) + // Retrieve location if location := computeResource.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 764219988d6b..7af5a8bb1b7c 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -31,7 +31,7 @@ func TestAccInferenceCluster_basic(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("cluster_purpose", "description"), + data.ImportStep(), }) } @@ -69,7 +69,7 @@ func TestAccInferenceCluster_complete(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl", "cluster_purpose", "description"), + data.ImportStep(), }) } @@ -88,7 +88,7 @@ func TestAccInferenceCluster_basicUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("cluster_purpose", "description"), + data.ImportStep(), { Config: r.basicUpdate(data), Check: resource.ComposeTestCheckFunc( @@ -99,7 +99,7 @@ func TestAccInferenceCluster_basicUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("cluster_purpose", "description"), + data.ImportStep(), }) } @@ -118,7 +118,7 @@ func TestAccInferenceCluster_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl", "cluster_purpose", "description"), + data.ImportStep(), { Config: r.completeUpdate(data), Check: resource.ComposeTestCheckFunc( @@ -129,7 +129,7 @@ func TestAccInferenceCluster_completeUpdate(t *testing.T) { check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, - data.ImportStep("ssl", "cluster_purpose", "description"), + data.ImportStep(), }) } @@ -161,7 +161,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "Dev" + cluster_purpose = "DevTest" node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id identity { @@ -181,7 +181,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "Dev" + cluster_purpose = "DevTest" node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id identity { @@ -205,7 +205,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "Test" + cluster_purpose = "DevTest" node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id ssl { cert = file("testdata/cert.pem") @@ -231,7 +231,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "Test" + cluster_purpose = "DevTest" node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id ssl { cert = file("testdata/cert.pem") From 8b23ccf74534709c9d7d3c913317a43bd9c37c71 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 12:21:18 +0200 Subject: [PATCH 14/33] simplification of `inference_cluster` --- ...ine_learning_inference_cluster_resource.go | 277 +++--------------- ...earning_inference_cluster_resource_test.go | 57 +--- 2 files changed, 49 insertions(+), 285 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 149d0ef43cb5..9e12f5e1700f 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -2,14 +2,12 @@ package machinelearning import ( "fmt" - "math" "time" "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-02-01/containerservice" "github.com/Azure/azure-sdk-for-go/services/machinelearningservices/mgmt/2020-04-01/machinelearningservices" "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" @@ -28,9 +26,8 @@ const minNumberOfNodesProd int32 = 12 func resourceAksInferenceCluster() *schema.Resource { return &schema.Resource{ - Create: resourceAksInferenceClusterCreateUpdate, + Create: resourceAksInferenceClusterCreate, Read: resourceAksInferenceClusterRead, - Update: resourceAksInferenceClusterCreateUpdate, Delete: resourceAksInferenceClusterDelete, Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { @@ -71,40 +68,7 @@ func resourceAksInferenceCluster() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "Dev", - }, - - "node_pool_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validate.NodePoolID, - }, - - "identity": { - Type: schema.TypeList, - Required: true, - ForceNew: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(machinelearningservices.SystemAssigned), - }, false), - }, - "principal_id": { - Type: schema.TypeString, - Computed: true, - }, - "tenant_id": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, + Default: "FastProd", }, "ssl": { @@ -117,16 +81,19 @@ func resourceAksInferenceCluster() *schema.Resource { "cert": { Type: schema.TypeString, Optional: true, + ForceNew: true, Default: "", }, "key": { Type: schema.TypeString, Optional: true, + ForceNew: true, Default: "", }, "cname": { Type: schema.TypeString, Optional: true, + ForceNew: true, Default: "", }, }, @@ -136,23 +103,17 @@ func resourceAksInferenceCluster() *schema.Resource { "description": { Type: schema.TypeString, Optional: true, + ForceNew: true, }, - "sku_name": { - Type: schema.TypeString, - Computed: true, - }, - - "tags": tags.Schema(), + "tags": tags.ForceNewSchema(), }, } } -func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interface{}) error { - mlWorkspacesClient := meta.(*clients.Client).MachineLearning.WorkspacesClient +func resourceAksInferenceClusterCreate(d *schema.ResourceData, meta interface{}) error { mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient aksClient := meta.(*clients.Client).Containers.KubernetesClustersClient - poolsClient := meta.(*clients.Client).Containers.AgentPoolsClient ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -160,34 +121,25 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf name := d.Get("name").(string) // Get Machine Learning Workspace Name and Resource Group from ID - unparsedWorkspaceID := d.Get("machine_learning_workspace_id").(string) - - workspaceID, err := parse.WorkspaceID(unparsedWorkspaceID) + workspaceID, err := parse.WorkspaceID(d.Get("machine_learning_workspace_id").(string)) if err != nil { return err } - - // Check if Inference Cluster already exists - existing, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("error checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspaceID.Name, workspaceID.ResourceGroup, err) + if d.IsNewResource() { + // Check if Inference Cluster already exists + existing, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspaceID.Name, workspaceID.ResourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_machine_learning_inference_cluster", *existing.ID) } } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_machine_learning_inference_cluster", *existing.ID) - } - - // Get SKU from Workspace - workspace, err := mlWorkspacesClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name) - if err != nil { - return err - } - sku := workspace.Sku // Get Kubernetes Cluster Name and Resource Group from ID - unparsedAksID := d.Get("kubernetes_cluster_id").(string) - aksID, err := parse.KubernetesClusterID(unparsedAksID) + aksID, err := parse.KubernetesClusterID(d.Get("kubernetes_cluster_id").(string)) if err != nil { return err } @@ -198,78 +150,36 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf return err } - unparsedAgentPoolID := d.Get("node_pool_id").(string) - agentPoolID, err := parse.NodePoolID(unparsedAgentPoolID ) - if err != nil { - return err - } - nodePool, err := poolsClient.Get(ctx, aksID.ResourceGroup, aksID.ManagedClusterName, agentPoolID.AgentPoolName) - if err != nil { - return err - } - - t := d.Get("tags").(map[string]interface{}) - - identity := d.Get("identity").([]interface{}) - sslInterface := d.Get("ssl").([]interface{}) - ssl := expandSSLConfig(sslInterface) - - unmappedClusterPurpose := d.Get("cluster_purpose").(string) - var mapClusterPurpose = map[string]string{ - "DevTest": "DevTest", - "Prod": "FastProd", - } - clusterPurpose := machinelearningservices.ClusterPurpose(mapClusterPurpose[unmappedClusterPurpose]) + ssl := expandSSLConfig(d.Get("ssl").([]interface{})) - aksProperties := expandAksProperties(&aks, &nodePool, ssl, clusterPurpose) + clusterPurpose := machinelearningservices.ClusterPurpose(d.Get("cluster_purpose").(string)) + aksProperties := expandAksProperties(&aks, ssl, clusterPurpose) location := azure.NormalizeLocation(d.Get("location").(string)) description := d.Get("description").(string) - aksComputeProperties := expandAksComputeProperties(aksProperties, &aks, location, description) - aksCompute, isAks := (machinelearningservices.BasicCompute).AsAKS(aksComputeProperties) + aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(expandAksComputeProperties(aksProperties, &aks, location, description)) if !isAks { return fmt.Errorf("error: No AKS cluster") } inferenceClusterParameters := machinelearningservices.ComputeResource{ - // Properties - Compute properties - Properties: aksCompute, - // ID - READ-ONLY; Specifies the resource ID. - // Name - READ-ONLY; Specifies the name of the resource. - // Identity - The identity of the resource. - Identity: expandAksInferenceClusterIdentity(identity), - // Location - Specifies the location of the resource. - Location: &location, - // Type - READ-ONLY; Specifies the type of the resource. - // Tags - Contains resource tags defined as key/value pairs. - Tags: tags.Expand(t), - // Sku - The sku of the workspace. - Sku: sku, + Properties: aksComputeProperties, + Location: &location, + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } if v, ok := d.GetOk("description"); ok { - aksCompute.Description = utils.String(v.(string)) + aksComputeProperties.Description = utils.String(v.(string)) } future, err := mlComputeClient.CreateOrUpdate(ctx, workspaceID.ResourceGroup, workspaceID.Name, name, inferenceClusterParameters) if err != nil { - return fmt.Errorf("error creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) + return fmt.Errorf("creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) } if err := future.WaitForCompletionRef(ctx, mlComputeClient.Client); err != nil { - return fmt.Errorf("error waiting for creation of Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) - } - resp, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) - if err != nil { - return fmt.Errorf("error retrieving Inference Cluster Compute %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) - } - - if resp.ID == nil { - return fmt.Errorf("cannot read Inference Cluster ID %q in workspace %q (Resource Group %q) ID", name, workspaceID.Name, workspaceID.ResourceGroup) - } - - id, err := parse.InferenceClusterID(*resp.ID) - if err != nil { - return err + return fmt.Errorf("waiting for creation of Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) } + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + id := parse.NewInferenceClusterID(subscriptionId, workspaceID.ResourceGroup, workspaceID.Name, name) d.SetId(id.ID()) return resourceAksInferenceClusterRead(d, meta) @@ -278,7 +188,6 @@ func resourceAksInferenceClusterCreateUpdate(d *schema.ResourceData, meta interf func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) error { mlWorkspacesClient := meta.(*clients.Client).MachineLearning.WorkspacesClient mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient - poolsClient := meta.(*clients.Client).Containers.AgentPoolsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -296,7 +205,7 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.SetId("") return nil } - return fmt.Errorf("error making Read request on Inference Cluster %q in Workspace %q (Resource Group %q): %+v", + return fmt.Errorf("making Read request on Inference Cluster %q in Workspace %q (Resource Group %q): %+v", id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) } @@ -308,54 +217,29 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.Set("machine_learning_workspace_id", mlResp.ID) // use ComputeResource to get to AKS Cluster ID and other properties - aksCompute, isAks := (machinelearningservices.BasicCompute).AsAKS(computeResource.Properties) + aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(computeResource.Properties) if !isAks { return fmt.Errorf("compute resource %s is not an AKS cluster", id.ComputeName) } - unparsedAksId := *aksCompute.ResourceID - // Retrieve AKS Cluster name and Node pool name from ID - aksId, err := parse.KubernetesClusterID(unparsedAksId) - if err != nil { - return err - } - - nodePoolList, _ := poolsClient.List(ctx, id.ResourceGroup, aksId.ManagedClusterName) - unparsedPoolId := *(nodePoolList.Values()[0].ID) - poolId, err := parse.NodePoolID(unparsedPoolId) + aksId, err := parse.KubernetesClusterID(*aksComputeProperties.ResourceID) if err != nil { return err } d.Set("kubernetes_cluster_id", aksId) - d.Set("node_pool_name", poolId.AgentPoolName) - var mapClusterPurpose = map[string]string{ - "DevTest": "DevTest", - "FastProd": "Prod", - } - d.Set("cluster_purpose", mapClusterPurpose[string(aksCompute.Properties.ClusterPurpose)]) + d.Set("cluster_purpose", string(aksComputeProperties.Properties.ClusterPurpose)) - d.Set("description", aksCompute.Description) - d.Set("ssl", aksCompute.Properties.SslConfiguration) + d.Set("description", aksComputeProperties.Description) + d.Set("ssl", aksComputeProperties.Properties.SslConfiguration) // Retrieve location if location := computeResource.Location; location != nil { d.Set("location", azure.NormalizeLocation(*location)) } - // Retrieve Sku - if sku := computeResource.Sku; sku != nil { - d.Set("sku_name", sku.Name) - } - - // Retrieve Identity - if err := d.Set("identity", flattenAksInferenceClusterIdentity(computeResource.Identity)); err != nil { - return fmt.Errorf("error flattening identity on Workspace %q (Resource Group %q): %+v", - id.WorkspaceName, id.ResourceGroup, err) - } - return tags.FlattenAndSet(d, computeResource.Tags) } @@ -370,11 +254,11 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) future, err := mlComputeClient.Delete(ctx, id.ResourceGroup, id.WorkspaceName, id.ComputeName, machinelearningservices.Detach) if err != nil { - return fmt.Errorf("error deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", + return fmt.Errorf("deleting Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) } if err := future.WaitForCompletionRef(ctx, mlComputeClient.Client); err != nil { - return fmt.Errorf("error waiting for deletion of Inference Cluster %q in workspace %q (Resource Group %q): %+v", + return fmt.Errorf("waiting for deletion of Inference Cluster %q in workspace %q (Resource Group %q): %+v", id.ComputeName, id.WorkspaceName, id.ResourceGroup, err) } return nil @@ -403,65 +287,24 @@ func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfigurat OverwriteExistingDomain: utils.Bool(v["overwrite_existing_domain"].(bool))} } -func expandAksNetworkingConfiguration(aks *containerservice.ManagedCluster, nodePool *containerservice.AgentPool) *machinelearningservices.AksNetworkingConfiguration { - subnet_id := *(nodePool.ManagedClusterAgentPoolProfileProperties).VnetSubnetID - service_cidr := *(aks.NetworkProfile.ServiceCidr) - dns_service_ip := *(aks.NetworkProfile.DNSServiceIP) - docker_bridge_cidr := *(aks.NetworkProfile.DockerBridgeCidr) - - return &machinelearningservices.AksNetworkingConfiguration{ - SubnetID: utils.String(subnet_id), - ServiceCidr: utils.String(service_cidr), - DNSServiceIP: utils.String(dns_service_ip), - DockerBridgeCidr: utils.String(docker_bridge_cidr)} -} - -func expandAksProperties(aks *containerservice.ManagedCluster, nodePool *containerservice.AgentPool, +func expandAksProperties(aks *containerservice.ManagedCluster, ssl *machinelearningservices.SslConfiguration, clusterPurpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { fqdn := *(aks.ManagedClusterProperties.Fqdn) - agentCount := *(nodePool.ManagedClusterAgentPoolProfileProperties).Count - agentVmSize := string(nodePool.ManagedClusterAgentPoolProfileProperties.VMSize) - - if agentCount < minNumberOfNodesProd && clusterPurpose == "FastProd" { - minNumberOfCores := int(math.Ceil(float64(minNumberOfNodesProd) / float64(agentCount))) - err := fmt.Errorf("error: you should pick a VM with at least %d cores", minNumberOfCores) - fmt.Println(err.Error()) - } return &machinelearningservices.AKSProperties{ - // ClusterFqdn - Cluster fully qualified domain name - ClusterFqdn: utils.String(fqdn), - // SystemServices - READ-ONLY; System services - // AgentCount - Number of agents - AgentCount: utils.Int32(agentCount), - // AgentVMSize - Agent virtual machine size - AgentVMSize: utils.String(agentVmSize), - // SslConfiguration - SSL configuration + ClusterFqdn: utils.String(fqdn), SslConfiguration: ssl, - // AksNetworkingConfiguration - AKS networking configuration for vnet - AksNetworkingConfiguration: expandAksNetworkingConfiguration(aks, nodePool), - // ClusterPurpose - Possible values include: 'FastProd', 'DenseProd', 'DevTest' - ClusterPurpose: clusterPurpose, + ClusterPurpose: clusterPurpose, } } func expandAksComputeProperties(aksProperties *machinelearningservices.AKSProperties, aks *containerservice.ManagedCluster, location string, description string) machinelearningservices.AKS { return machinelearningservices.AKS{ - // Properties - AKS properties - Properties: aksProperties, - // ComputeLocation - Location for the underlying compute + Properties: aksProperties, ComputeLocation: &location, - // ProvisioningState - READ-ONLY; The provision state of the cluster. Valid values are Unknown, Updating, Provisioning, Succeeded, and Failed. Possible values include: 'ProvisioningStateUnknown', 'ProvisioningStateUpdating', 'ProvisioningStateCreating', 'ProvisioningStateDeleting', 'ProvisioningStateSucceeded', 'ProvisioningStateFailed', 'ProvisioningStateCanceled' - // Description - The description of the Machine Learning compute. - Description: &description, - // CreatedOn - READ-ONLY; The date and time when the compute was created. - // ModifiedOn - READ-ONLY; The date and time when the compute was last modified. - // ResourceID - ARM resource id of the underlying compute - ResourceID: aks.ID, - // ProvisioningErrors - READ-ONLY; Errors during provisioning - // IsAttachedCompute - READ-ONLY; Indicating whether the compute was provisioned by user and brought from outside if true, or machine learning service provisioned it if false. - // ComputeType - Possible values include: 'ComputeTypeCompute', 'ComputeTypeAKS1', 'ComputeTypeAmlCompute1', 'ComputeTypeVirtualMachine1', 'ComputeTypeHDInsight1', 'ComputeTypeDataFactory1', 'ComputeTypeDatabricks1', 'ComputeTypeDataLakeAnalytics1' - ComputeType: "ComputeTypeAKS1", + Description: &description, + ResourceID: aks.ID, + ComputeType: "ComputeTypeAKS1", } } @@ -476,29 +319,3 @@ func expandAksInferenceClusterIdentity(input []interface{}) *machinelearningserv Type: machinelearningservices.ResourceIdentityType(v["type"].(string)), } } - -func flattenAksInferenceClusterIdentity(identity *machinelearningservices.Identity) []interface{} { - if identity == nil { - return []interface{}{} - } - - t := string(identity.Type) - - principalID := "" - if identity.PrincipalID != nil { - principalID = *identity.PrincipalID - } - - tenantID := "" - if identity.TenantID != nil { - tenantID = *identity.TenantID - } - - return []interface{}{ - map[string]interface{}{ - "type": t, - "principal_id": principalID, - "tenant_id": tenantID, - }, - } -} diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 7af5a8bb1b7c..1039de440ed7 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -25,10 +25,6 @@ func TestAccInferenceCluster_basic(t *testing.T) { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.ImportStep(), @@ -44,10 +40,6 @@ func TestAccInferenceCluster_requiresImport(t *testing.T) { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.RequiresImportErrorStep(r.requiresImport), @@ -63,10 +55,6 @@ func TestAccInferenceCluster_complete(t *testing.T) { Config: r.complete(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.ImportStep(), @@ -82,10 +70,6 @@ func TestAccInferenceCluster_basicUpdate(t *testing.T) { Config: r.basic(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.ImportStep(), @@ -93,10 +77,6 @@ func TestAccInferenceCluster_basicUpdate(t *testing.T) { Config: r.basicUpdate(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.ImportStep(), @@ -112,10 +92,6 @@ func TestAccInferenceCluster_completeUpdate(t *testing.T) { Config: r.complete(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.ImportStep(), @@ -123,10 +99,6 @@ func TestAccInferenceCluster_completeUpdate(t *testing.T) { Config: r.completeUpdate(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("identity.#").HasValue("1"), - check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), - check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), - check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), ), }, data.ImportStep(), @@ -162,11 +134,6 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "DevTest" - node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id - - identity { - type = "SystemAssigned" - } } `, template, data.RandomIntOfLength(8)) } @@ -182,11 +149,6 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "DevTest" - node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id - - identity { - type = "SystemAssigned" - } tags = { ENV = "Test" @@ -206,17 +168,12 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "DevTest" - node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id ssl { cert = file("testdata/cert.pem") key = file("testdata/key.pem") cname = "www.contoso.com" } - identity { - type = "SystemAssigned" - } - } `, template, data.RandomIntOfLength(8)) } @@ -232,17 +189,12 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "DevTest" - node_pool_id = azurerm_kubernetes_cluster.test.default_node_pool[0].id ssl { cert = file("testdata/cert.pem") key = file("testdata/key.pem") cname = "www.contoso.com" } - identity { - type = "SystemAssigned" - } - tags = { ENV = "Test" } @@ -261,13 +213,8 @@ resource "azurerm_machine_learning_inference_cluster" "import" { machine_learning_workspace_id = azurerm_machine_learning_inference_cluster.test.machine_learning_workspace_id location = azurerm_machine_learning_inference_cluster.test.location kubernetes_cluster_id = azurerm_machine_learning_inference_cluster.test.kubernetes_cluster_id - node_pool_id = azurerm_machine_learning_inference_cluster.test.node_pool_id cluster_purpose = azurerm_machine_learning_inference_cluster.test.cluster_purpose - identity { - type = "SystemAssigned" - } - tags = azurerm_machine_learning_inference_cluster.test.tags } `, template) @@ -351,8 +298,8 @@ resource "azurerm_kubernetes_cluster" "test" { default_node_pool { name = "default" - node_count = 3 - vm_size = "Standard_D11_v2" + node_count = 1 + vm_size = "Standard_DS2_v2" vnet_subnet_id = azurerm_subnet.test.id } From 6912f3eb7e99244b2f659aa3c4dffcbbc06b43fe Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 12:53:25 +0200 Subject: [PATCH 15/33] cleanup and fix aks id --- ...ine_learning_inference_cluster_resource.go | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 9e12f5e1700f..deaa6abcd016 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -49,14 +49,6 @@ func resourceAksInferenceCluster() *schema.Resource { ForceNew: true, }, - "machine_learning_workspace_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "location": azure.SchemaLocation(), - "kubernetes_cluster_id": { Type: schema.TypeString, Required: true, @@ -64,6 +56,14 @@ func resourceAksInferenceCluster() *schema.Resource { ValidateFunc: validate.KubernetesClusterID, }, + "location": azure.SchemaLocation(), + + "machine_learning_workspace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "cluster_purpose": { Type: schema.TypeString, Optional: true, @@ -71,6 +71,12 @@ func resourceAksInferenceCluster() *schema.Resource { Default: "FastProd", }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "ssl": { Type: schema.TypeList, Optional: true, @@ -100,12 +106,6 @@ func resourceAksInferenceCluster() *schema.Resource { }, }, - "description": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "tags": tags.ForceNewSchema(), }, } @@ -125,18 +125,17 @@ func resourceAksInferenceClusterCreate(d *schema.ResourceData, meta interface{}) if err != nil { return err } - if d.IsNewResource() { - // Check if Inference Cluster already exists - existing, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspaceID.Name, workspaceID.ResourceGroup, err) - } - } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_machine_learning_inference_cluster", *existing.ID) + + // Check if Inference Cluster already exists + existing, err := mlComputeClient.Get(ctx, workspaceID.ResourceGroup, workspaceID.Name, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for existing Inference Cluster %q in Workspace %q (Resource Group %q): %s", name, workspaceID.Name, workspaceID.ResourceGroup, err) } } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_machine_learning_inference_cluster", *existing.ID) + } // Get Kubernetes Cluster Name and Resource Group from ID aksID, err := parse.KubernetesClusterID(d.Get("kubernetes_cluster_id").(string)) @@ -222,16 +221,13 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("compute resource %s is not an AKS cluster", id.ComputeName) } - // Retrieve AKS Cluster name and Node pool name from ID + // Retrieve AKS Cluster ID aksId, err := parse.KubernetesClusterID(*aksComputeProperties.ResourceID) if err != nil { return err } - - d.Set("kubernetes_cluster_id", aksId) - + d.Set("kubernetes_cluster_id", aksId.ID()) d.Set("cluster_purpose", string(aksComputeProperties.Properties.ClusterPurpose)) - d.Set("description", aksComputeProperties.Description) d.Set("ssl", aksComputeProperties.Properties.SslConfiguration) From f4038877f8b7e5b11816609e7a6d23186322241d Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 13:46:12 +0200 Subject: [PATCH 16/33] DiffSuppresFunc for AKS ID added --- .../machine_learning_inference_cluster_resource.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index deaa6abcd016..269a11cec0ae 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -18,6 +18,7 @@ import ( "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/tf/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -54,6 +55,8 @@ func resourceAksInferenceCluster() *schema.Resource { Required: true, ForceNew: true, ValidateFunc: validate.KubernetesClusterID, + // remove in 3.0 of the provider + DiffSuppressFunc: suppress.CaseDifference, }, "location": azure.SchemaLocation(), From ed3c5e3ca5ca109ae8d3e1455cad5feabe62ea5e Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 13:46:57 +0200 Subject: [PATCH 17/33] Stringify ssl variables --- .../machine_learning_inference_cluster_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 269a11cec0ae..f64fefeef758 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -273,7 +273,7 @@ func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfigurat // SSL Certificate default values sslStatus := "Disabled" - if !(v["cert"] == "" && v["key"] == "" && v["cname"] == "") { + if !(v["cert"].(string) == "" && v["key"].(string) == "" && v["cname"].(string) == "") { sslStatus = "Enabled" } From 177ff7913d51a617c2dbe9588f308c62de90af27 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 13:47:24 +0200 Subject: [PATCH 18/33] Error msg fix --- .../machine_learning_inference_cluster_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index f64fefeef758..d2810ebf9dd5 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -160,7 +160,7 @@ func resourceAksInferenceClusterCreate(d *schema.ResourceData, meta interface{}) description := d.Get("description").(string) aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(expandAksComputeProperties(aksProperties, &aks, location, description)) if !isAks { - return fmt.Errorf("error: No AKS cluster") + return fmt.Errorf("the AKS Compute resource is not recognized as AKS Compute") } inferenceClusterParameters := machinelearningservices.ComputeResource{ From 490af9e80fb456d1f6e8b89b7586def33eca217e Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 14:17:41 +0200 Subject: [PATCH 19/33] Fix linter issues --- ...ine_learning_inference_cluster_resource.go | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index d2810ebf9dd5..828a849edfe8 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -23,8 +23,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -const minNumberOfNodesProd int32 = 12 - func resourceAksInferenceCluster() *schema.Resource { return &schema.Resource{ Create: resourceAksInferenceClusterCreate, @@ -232,7 +230,7 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.Set("kubernetes_cluster_id", aksId.ID()) d.Set("cluster_purpose", string(aksComputeProperties.Properties.ClusterPurpose)) d.Set("description", aksComputeProperties.Description) - d.Set("ssl", aksComputeProperties.Properties.SslConfiguration) + d.Set("ssl", flattenSSLConfig(aksComputeProperties.Properties.SslConfiguration)) // Retrieve location if location := computeResource.Location; location != nil { @@ -263,6 +261,20 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) return nil } +func flattenSSLConfig(sslConfig *machinelearningservices.SslConfiguration) []interface{} { + if sslConfig == nil { + return []interface{}{} + } + + return []interface{}{ + map[string]interface{}{ + "cert": utils.String(*sslConfig.Cert), + "cname": utils.String(*sslConfig.Cname), + "key": utils.String(*sslConfig.Key), + }, + } +} + func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfiguration { if len(input) == 0 { return nil @@ -278,12 +290,11 @@ func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfigurat } return &machinelearningservices.SslConfiguration{ - Status: machinelearningservices.Status1(sslStatus), - Cert: utils.String(v["cert"].(string)), - Key: utils.String(v["key"].(string)), - Cname: utils.String(v["cname"].(string)), - LeafDomainLabel: utils.String(v["leaf_domain_label"].(string)), - OverwriteExistingDomain: utils.Bool(v["overwrite_existing_domain"].(bool))} + Status: machinelearningservices.Status1(sslStatus), + Cert: utils.String(v["cert"].(string)), + Key: utils.String(v["key"].(string)), + Cname: utils.String(v["cname"].(string)), + } } func expandAksProperties(aks *containerservice.ManagedCluster, @@ -306,15 +317,3 @@ func expandAksComputeProperties(aksProperties *machinelearningservices.AKSProper ComputeType: "ComputeTypeAKS1", } } - -func expandAksInferenceClusterIdentity(input []interface{}) *machinelearningservices.Identity { - if len(input) == 0 { - return nil - } - - v := input[0].(map[string]interface{}) - - return &machinelearningservices.Identity{ - Type: machinelearningservices.ResourceIdentityType(v["type"].(string)), - } -} From 982e37f40a45fe52a8bc7b043e3e6e26f4b446d5 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 15:04:27 +0200 Subject: [PATCH 20/33] Fix ssl configuration --- ...ine_learning_inference_cluster_resource.go | 15 ---- ...earning_inference_cluster_resource_test.go | 81 +------------------ 2 files changed, 1 insertion(+), 95 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 828a849edfe8..5aacb12e6dbc 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -230,7 +230,6 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e d.Set("kubernetes_cluster_id", aksId.ID()) d.Set("cluster_purpose", string(aksComputeProperties.Properties.ClusterPurpose)) d.Set("description", aksComputeProperties.Description) - d.Set("ssl", flattenSSLConfig(aksComputeProperties.Properties.SslConfiguration)) // Retrieve location if location := computeResource.Location; location != nil { @@ -261,20 +260,6 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) return nil } -func flattenSSLConfig(sslConfig *machinelearningservices.SslConfiguration) []interface{} { - if sslConfig == nil { - return []interface{}{} - } - - return []interface{}{ - map[string]interface{}{ - "cert": utils.String(*sslConfig.Cert), - "cname": utils.String(*sslConfig.Cname), - "key": utils.String(*sslConfig.Key), - }, - } -} - func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfiguration { if len(input) == 0 { return nil diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 1039de440ed7..9e593e269ca5 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -57,51 +57,7 @@ func TestAccInferenceCluster_complete(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - data.ImportStep(), - }) -} - -func TestAccInferenceCluster_basicUpdate(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") - r := InferenceClusterResource{} - - data.ResourceTest(t, r, []resource.TestStep{ - { - Config: r.basic(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - { - Config: r.basicUpdate(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - }) -} - -func TestAccInferenceCluster_completeUpdate(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") - r := InferenceClusterResource{} - - data.ResourceTest(t, r, []resource.TestStep{ - { - Config: r.complete(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - { - Config: r.completeUpdate(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), + data.ImportStep("ssl"), }) } @@ -134,21 +90,7 @@ resource "azurerm_machine_learning_inference_cluster" "test" { location = azurerm_resource_group.test.location kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id cluster_purpose = "DevTest" -} -`, template, data.RandomIntOfLength(8)) -} -func (r InferenceClusterResource) basicUpdate(data acceptance.TestData) string { - template := r.template(data) - return fmt.Sprintf(` -%s - -resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" - machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id - location = azurerm_resource_group.test.location - kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "DevTest" tags = { ENV = "Test" @@ -162,27 +104,6 @@ func (r InferenceClusterResource) complete(data acceptance.TestData) string { return fmt.Sprintf(` %s -resource "azurerm_machine_learning_inference_cluster" "test" { - name = "AIC-%d" - machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id - location = azurerm_resource_group.test.location - kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id - cluster_purpose = "DevTest" - ssl { - cert = file("testdata/cert.pem") - key = file("testdata/key.pem") - cname = "www.contoso.com" - } - -} -`, template, data.RandomIntOfLength(8)) -} - -func (r InferenceClusterResource) completeUpdate(data acceptance.TestData) string { - template := r.template(data) - return fmt.Sprintf(` -%s - resource "azurerm_machine_learning_inference_cluster" "test" { name = "AIC-%d" machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id From 52782d984b2e3be7b0fae161bc3360c5884eee23 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 17:00:22 +0200 Subject: [PATCH 21/33] Remove AKS clients from ML client package --- .../services/machinelearning/client/client.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/azurerm/internal/services/machinelearning/client/client.go b/azurerm/internal/services/machinelearning/client/client.go index d29aaf6331a6..59aeb54ec48a 100644 --- a/azurerm/internal/services/machinelearning/client/client.go +++ b/azurerm/internal/services/machinelearning/client/client.go @@ -1,7 +1,6 @@ package client import ( - "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-02-01/containerservice" "github.com/Azure/azure-sdk-for-go/services/machinelearningservices/mgmt/2020-04-01/machinelearningservices" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" ) @@ -9,8 +8,6 @@ import ( type Client struct { WorkspacesClient *machinelearningservices.WorkspacesClient MachineLearningComputeClient *machinelearningservices.MachineLearningComputeClient - KubernetesClustersClient *containerservice.ManagedClustersClient - AgentPoolsClient *containerservice.AgentPoolsClient } func NewClient(o *common.ClientOptions) *Client { @@ -20,16 +17,8 @@ func NewClient(o *common.ClientOptions) *Client { MachineLearningComputeClient := machinelearningservices.NewMachineLearningComputeClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&MachineLearningComputeClient.Client, o.ResourceManagerAuthorizer) - KubernetesClustersClient := containerservice.NewManagedClustersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&KubernetesClustersClient.Client, o.ResourceManagerAuthorizer) - - agentPoolsClient := containerservice.NewAgentPoolsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) - o.ConfigureClient(&agentPoolsClient.Client, o.ResourceManagerAuthorizer) - return &Client{ WorkspacesClient: &WorkspacesClient, MachineLearningComputeClient: &MachineLearningComputeClient, - KubernetesClustersClient: &KubernetesClustersClient, - AgentPoolsClient: &agentPoolsClient, } } From 748bf9e4cda5f23c5e3b134cd1d5e4a4665188c5 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 17:01:02 +0200 Subject: [PATCH 22/33] Simplify expand for AKS Compute properties --- ...ine_learning_inference_cluster_resource.go | 56 ++++++------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 5aacb12e6dbc..eae84abcf3b2 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -138,39 +138,26 @@ func resourceAksInferenceClusterCreate(d *schema.ResourceData, meta interface{}) return tf.ImportAsExistsError("azurerm_machine_learning_inference_cluster", *existing.ID) } - // Get Kubernetes Cluster Name and Resource Group from ID + // Get AKS Compute Properties aksID, err := parse.KubernetesClusterID(d.Get("kubernetes_cluster_id").(string)) if err != nil { return err } - - // Get Existing AKS aks, err := aksClient.Get(ctx, aksID.ResourceGroup, aksID.ManagedClusterName) if err != nil { return err } - - ssl := expandSSLConfig(d.Get("ssl").([]interface{})) - - clusterPurpose := machinelearningservices.ClusterPurpose(d.Get("cluster_purpose").(string)) - aksProperties := expandAksProperties(&aks, ssl, clusterPurpose) - location := azure.NormalizeLocation(d.Get("location").(string)) - description := d.Get("description").(string) - aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(expandAksComputeProperties(aksProperties, &aks, location, description)) + aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(expandAksComputeProperties(&aks, d)) if !isAks { - return fmt.Errorf("the AKS Compute resource is not recognized as AKS Compute") + return fmt.Errorf("the Compute Properties are not recognized as AKS Compute Properties") } inferenceClusterParameters := machinelearningservices.ComputeResource{ Properties: aksComputeProperties, - Location: &location, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } - if v, ok := d.GetOk("description"); ok { - aksComputeProperties.Description = utils.String(v.(string)) - } - future, err := mlComputeClient.CreateOrUpdate(ctx, workspaceID.ResourceGroup, workspaceID.Name, name, inferenceClusterParameters) if err != nil { return fmt.Errorf("creating Inference Cluster %q in workspace %q (Resource Group %q): %+v", name, workspaceID.Name, workspaceID.ResourceGroup, err) @@ -260,6 +247,20 @@ func resourceAksInferenceClusterDelete(d *schema.ResourceData, meta interface{}) return nil } +func expandAksComputeProperties(aks *containerservice.ManagedCluster, d *schema.ResourceData) machinelearningservices.AKS { + return machinelearningservices.AKS{ + Properties: &machinelearningservices.AKSProperties{ + ClusterFqdn: utils.String(*aks.Fqdn), + SslConfiguration: expandSSLConfig(d.Get("ssl").([]interface{})), + ClusterPurpose: machinelearningservices.ClusterPurpose(d.Get("cluster_purpose").(string)), + }, + ComputeLocation: aks.Location, + Description: utils.String(d.Get("description").(string)), + ResourceID: aks.ID, + ComputeType: "ComputeTypeAKS1", + } +} + func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfiguration { if len(input) == 0 { return nil @@ -281,24 +282,3 @@ func expandSSLConfig(input []interface{}) *machinelearningservices.SslConfigurat Cname: utils.String(v["cname"].(string)), } } - -func expandAksProperties(aks *containerservice.ManagedCluster, - ssl *machinelearningservices.SslConfiguration, clusterPurpose machinelearningservices.ClusterPurpose) *machinelearningservices.AKSProperties { - fqdn := *(aks.ManagedClusterProperties.Fqdn) - - return &machinelearningservices.AKSProperties{ - ClusterFqdn: utils.String(fqdn), - SslConfiguration: ssl, - ClusterPurpose: clusterPurpose, - } -} - -func expandAksComputeProperties(aksProperties *machinelearningservices.AKSProperties, aks *containerservice.ManagedCluster, location string, description string) machinelearningservices.AKS { - return machinelearningservices.AKS{ - Properties: aksProperties, - ComputeLocation: &location, - Description: &description, - ResourceID: aks.ID, - ComputeType: "ComputeTypeAKS1", - } -} From b5bd269a08a2713ce3b2c015fb3fc1eb365e9c48 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 23:16:43 +0200 Subject: [PATCH 23/33] Add production acctest --- ...earning_inference_cluster_resource_test.go | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index 9e593e269ca5..e3774b1f1d2d 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -61,6 +61,21 @@ func TestAccInferenceCluster_complete(t *testing.T) { }) } +func TestAccInferenceCluster_completeProduction(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_inference_cluster", "test") + r := InferenceClusterResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.completeProduction(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("ssl"), + }) +} + func (r InferenceClusterResource) Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) { inferenceClusterClient := client.MachineLearning.MachineLearningComputeClient id, err := parse.InferenceClusterID(state.ID) @@ -80,7 +95,6 @@ func (r InferenceClusterResource) Exists(ctx context.Context, client *clients.Cl } func (r InferenceClusterResource) basic(data acceptance.TestData) string { - template := r.template(data) return fmt.Sprintf(` %s @@ -96,11 +110,10 @@ resource "azurerm_machine_learning_inference_cluster" "test" { ENV = "Test" } } -`, template, data.RandomIntOfLength(8)) +`, r.templateDevTest(data), data.RandomIntOfLength(8)) } func (r InferenceClusterResource) complete(data acceptance.TestData) string { - template := r.template(data) return fmt.Sprintf(` %s @@ -121,11 +134,34 @@ resource "azurerm_machine_learning_inference_cluster" "test" { } } -`, template, data.RandomIntOfLength(8)) +`, r.templateDevTest(data), data.RandomIntOfLength(8)) +} + +func (r InferenceClusterResource) completeProduction(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_machine_learning_inference_cluster" "test" { + name = "AIC-%d" + machine_learning_workspace_id = azurerm_machine_learning_workspace.test.id + location = azurerm_resource_group.test.location + kubernetes_cluster_id = azurerm_kubernetes_cluster.test.id + cluster_purpose = "FastProd" + ssl { + cert = file("testdata/cert.pem") + key = file("testdata/key.pem") + cname = "www.contoso.com" + } + + tags = { + ENV = "Production" + } + +} +`, r.templateFastProd(data), data.RandomIntOfLength(8)) } func (r InferenceClusterResource) requiresImport(data acceptance.TestData) string { - template := r.basic(data) return fmt.Sprintf(` %s @@ -138,10 +174,17 @@ resource "azurerm_machine_learning_inference_cluster" "import" { tags = azurerm_machine_learning_inference_cluster.test.tags } -`, template) +`, r.basic(data)) +} + +func (r InferenceClusterResource) templateFastProd(data acceptance.TestData) string { + return r.template(data, "SStandard_D3_v2", 3) +} +func (r InferenceClusterResource) templateDevTest(data acceptance.TestData) string { + return r.template(data, "Standard_DS2_v2", 1) } -func (r InferenceClusterResource) template(data acceptance.TestData) string { +func (r InferenceClusterResource) template(data acceptance.TestData, vmSize string, nodeCount int) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -219,8 +262,8 @@ resource "azurerm_kubernetes_cluster" "test" { default_node_pool { name = "default" - node_count = 1 - vm_size = "Standard_DS2_v2" + node_count = %d + vm_size = "%s" vnet_subnet_id = azurerm_subnet.test.id } @@ -230,5 +273,5 @@ resource "azurerm_kubernetes_cluster" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomIntOfLength(12), data.RandomIntOfLength(15), data.RandomIntOfLength(16), - data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) + data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, nodeCount, vmSize) } From e35934e8d88153d5e44c80276942d161cb6a84a8 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Fri, 14 May 2021 23:16:57 +0200 Subject: [PATCH 24/33] Update docs --- ...e_learning_inference_cluster.html.markdown | 124 ++++++++++++++---- 1 file changed, 98 insertions(+), 26 deletions(-) diff --git a/website/docs/r/machine_learning_inference_cluster.html.markdown b/website/docs/r/machine_learning_inference_cluster.html.markdown index 821963cf69ee..7e61401a6853 100644 --- a/website/docs/r/machine_learning_inference_cluster.html.markdown +++ b/website/docs/r/machine_learning_inference_cluster.html.markdown @@ -10,19 +10,103 @@ description: |- Manages a Machine Learning Inference Cluster. +~> **NOTE:** The Machine Learning Inference Cluster resource is used to attach an existing AKS cluster to the Machine Learning Workspace, it doesn't create the AKS cluster itself. Therefore it can only be created and deleted, not updated. Any change to the configuration will recreate the resource. + ## Example Usage ```hcl -resource "azurerm_machine_learning_inference_cluster" "example" { - name = "example" - location = "West Europe" - kubernetes_cluster_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1" +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "west europe" + tags = { + "stage" = "example" + } +} + +resource "azurerm_application_insights" "example" { + name = "example-ai" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + application_type = "web" +} + +resource "azurerm_key_vault" "example" { + name = "example-kv" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + + sku_name = "standard" + + purge_protection_enabled = true +} + +resource "azurerm_storage_account" "example" { + name = "examplesa" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_machine_learning_workspace" "example" { + name = "example-mlw" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + application_insights_id = azurerm_application_insights.example.id + key_vault_id = azurerm_key_vault.example.id + storage_account_id = azurerm_storage_account.example.id identity { type = "SystemAssigned" } - machine_learning_workspace_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1" - node_pool_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1" +} + +resource "azurerm_virtual_network" "example" { + name = "example-vnet" + address_space = ["10.1.0.0/16"] + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name +} + +resource "azurerm_subnet" "example" { + name = "example-subnet" + resource_group_name = azurerm_resource_group.example.name + virtual_network_name = azurerm_virtual_network.example.name + address_prefix = "10.1.0.0/24" +} + +resource "azurerm_kubernetes_cluster" "example" { + name = "example-aks" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + default_node_pool { + name = "default" + node_count = 3 + vm_size = "Standard_D3_v2" + vnet_subnet_id = azurerm_subnet.example.id + } + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_machine_learning_inference_cluster" "example" { + name = "example" + location = azurerm_resource_group.example.location + cluster_purpose = "FastProd" + kubernetes_cluster_id = azurerm_kubernetes_cluster.example.id + description = "This is an example cluster used with Terraform" + + machine_learning_workspace_id = azurerm_machine_learning_workspace.example.id + + tags = { + "stage" = "example" + } } ``` @@ -30,7 +114,7 @@ resource "azurerm_machine_learning_inference_cluster" "example" { The following arguments are supported: -* `identity` - (Required) A `identity` block as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. +* `name` - (Required) The name which should be used for this Machine Learning Inference Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. * `kubernetes_cluster_id` - (Required) The ID of the Kubernetes Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. @@ -38,35 +122,26 @@ The following arguments are supported: * `machine_learning_workspace_id` - (Required) The ID of the Machine Learning Workspace. Changing this forces a new Machine Learning Inference Cluster to be created. -* `name` - (Required) The name which should be used for this Machine Learning Inference Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. - -* `node_pool_id` - (Required) The ID of the Kubernetes Cluster's node pool. Changing this forces a new Machine Learning Inference Cluster to be created. - ---- +* `cluster_purpose` - (Optional) The purpose of the Inference Cluster. If used for Development or Testing, use "DevTest" here. Default purpose is "FastProd", which is recommended for production workloads. Changing this forces a new Machine Learning Inference Cluster to be created. -* `cluster_purpose` - (Optional) The purpose of the Inference Cluster. If used for Development or Testing, use "Dev" or "Test" here. If using for production use "Prod" here. Changing this forces a new Machine Learning Inference Cluster to be created. +~> **NOTE:** When creating or attaching a cluster, if the cluster will be used for production (`cluster_purpose = "FastProd"`), then it must contain at least 12 virtual CPUs. The number of virtual CPUs can be calculated by multiplying the number of nodes in the cluster by the number of cores provided by the VM size selected. For example, if you use a VM size of "Standard_D3_v2", which has 4 virtual cores, then you should select 3 or greater as the number of nodes. * `description` - (Optional) The description of the Machine Learning compute. -* `ssl` - (Optional) A `ssl` block as defined below. Changing this forces a new Machine Learning Inference Cluster to be created. - -* `tags` - (Optional) A mapping of tags which should be assigned to the Machine Learning Inference Cluster. +* `ssl` - (Optional) A `ssl` block as defined below. ---- +* `tags` - (Optional) A mapping of tags which should be assigned to the Machine Learning Inference Cluster. Changing this forces a new Machine Learning Inference Cluster to be created. -A `identity` block supports the following: - -* `type` - (Required) The Type of Identity which should be used for this Disk Encryption Set. At this time the only possible value is `SystemAssigned`. --- A `ssl` block supports the following: -* `cert` - (Optional) The certificate for the ssl configuration. +* `cert` - (Optional) The certificate for the ssl configuration. Changing this forces a new Machine Learning Inference Cluster to be created. -* `cname` - (Optional) The cname of the ssl configuration. +* `cname` - (Optional) The cname of the ssl configuration. Changing this forces a new Machine Learning Inference Cluster to be created. -* `key` - (Optional) The key content for the ssl configuration. +* `key` - (Optional) The key content for the ssl configuration. Changing this forces a new Machine Learning Inference Cluster to be created. ## Attributes Reference @@ -74,15 +149,12 @@ In addition to the Arguments listed above - the following Attributes are exporte * `id` - The ID of the Machine Learning Inference Cluster. -* `sku_name` - The type of SKU. - ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: * `create` - (Defaults to 30 minutes) Used when creating the Machine Learning Inference Cluster. * `read` - (Defaults to 5 minutes) Used when retrieving the Machine Learning Inference Cluster. -* `update` - (Defaults to 30 minutes) Used when updating the Machine Learning Inference Cluster. * `delete` - (Defaults to 30 minutes) Used when deleting the Machine Learning Inference Cluster. ## Import From 7fc364642f6b5a996bc77943aad9644bfd3e68b8 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sat, 15 May 2021 17:33:36 +0200 Subject: [PATCH 25/33] Remove superfluous node pool parse and validate files --- .../machinelearning/parse/node_pool.go | 75 ---------- .../machinelearning/parse/node_pool_test.go | 128 ------------------ .../machinelearning/validate/node_pool_id.go | 23 ---- .../validate/node_pool_id_test.go | 88 ------------ 4 files changed, 314 deletions(-) delete mode 100644 azurerm/internal/services/machinelearning/parse/node_pool.go delete mode 100644 azurerm/internal/services/machinelearning/parse/node_pool_test.go delete mode 100644 azurerm/internal/services/machinelearning/validate/node_pool_id.go delete mode 100644 azurerm/internal/services/machinelearning/validate/node_pool_id_test.go diff --git a/azurerm/internal/services/machinelearning/parse/node_pool.go b/azurerm/internal/services/machinelearning/parse/node_pool.go deleted file mode 100644 index 04f0930ed519..000000000000 --- a/azurerm/internal/services/machinelearning/parse/node_pool.go +++ /dev/null @@ -1,75 +0,0 @@ -package parse - -// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten - -import ( - "fmt" - "strings" - - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" -) - -type NodePoolId struct { - SubscriptionId string - ResourceGroup string - ManagedClusterName string - AgentPoolName string -} - -func NewNodePoolID(subscriptionId, resourceGroup, managedClusterName, agentPoolName string) NodePoolId { - return NodePoolId{ - SubscriptionId: subscriptionId, - ResourceGroup: resourceGroup, - ManagedClusterName: managedClusterName, - AgentPoolName: agentPoolName, - } -} - -func (id NodePoolId) String() string { - segments := []string{ - fmt.Sprintf("Agent Pool Name %q", id.AgentPoolName), - fmt.Sprintf("Managed Cluster Name %q", id.ManagedClusterName), - fmt.Sprintf("Resource Group %q", id.ResourceGroup), - } - segmentsStr := strings.Join(segments, " / ") - return fmt.Sprintf("%s: (%s)", "Node Pool", segmentsStr) -} - -func (id NodePoolId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s/agentPools/%s" - return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ManagedClusterName, id.AgentPoolName) -} - -// NodePoolID parses a NodePool ID into an NodePoolId struct -func NodePoolID(input string) (*NodePoolId, error) { - id, err := azure.ParseAzureResourceID(input) - if err != nil { - return nil, err - } - - resourceId := NodePoolId{ - SubscriptionId: id.SubscriptionID, - ResourceGroup: id.ResourceGroup, - } - - if resourceId.SubscriptionId == "" { - return nil, fmt.Errorf("ID was missing the 'subscriptions' element") - } - - if resourceId.ResourceGroup == "" { - return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") - } - - if resourceId.ManagedClusterName, err = id.PopSegment("managedClusters"); err != nil { - return nil, err - } - if resourceId.AgentPoolName, err = id.PopSegment("agentPools"); err != nil { - return nil, err - } - - if err := id.ValidateNoEmptySegments(input); err != nil { - return nil, err - } - - return &resourceId, nil -} diff --git a/azurerm/internal/services/machinelearning/parse/node_pool_test.go b/azurerm/internal/services/machinelearning/parse/node_pool_test.go deleted file mode 100644 index 73f280445fa4..000000000000 --- a/azurerm/internal/services/machinelearning/parse/node_pool_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package parse - -// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten - -import ( - "testing" - - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" -) - -var _ resourceid.Formatter = NodePoolId{} - -func TestNodePoolIDFormatter(t *testing.T) { - actual := NewNodePoolID("00000000-0000-0000-0000-000000000000", "resGroup1", "cluster1", "pool1").ID() - expected := "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1" - if actual != expected { - t.Fatalf("Expected %q but got %q", expected, actual) - } -} - -func TestNodePoolID(t *testing.T) { - testData := []struct { - Input string - Error bool - Expected *NodePoolId - }{ - - { - // empty - Input: "", - Error: true, - }, - - { - // missing SubscriptionId - Input: "/", - Error: true, - }, - - { - // missing value for SubscriptionId - Input: "/subscriptions/", - Error: true, - }, - - { - // missing ResourceGroup - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", - Error: true, - }, - - { - // missing value for ResourceGroup - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", - Error: true, - }, - - { - // missing ManagedClusterName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", - Error: true, - }, - - { - // missing value for ManagedClusterName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", - Error: true, - }, - - { - // missing AgentPoolName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/", - Error: true, - }, - - { - // missing value for AgentPoolName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/", - Error: true, - }, - - { - // valid - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1", - Expected: &NodePoolId{ - SubscriptionId: "00000000-0000-0000-0000-000000000000", - ResourceGroup: "resGroup1", - ManagedClusterName: "cluster1", - AgentPoolName: "pool1", - }, - }, - - { - // upper-cased - Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1/AGENTPOOLS/POOL1", - Error: true, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Input) - - actual, err := NodePoolID(v.Input) - if err != nil { - if v.Error { - continue - } - - t.Fatalf("Expect a value but got an error: %s", err) - } - if v.Error { - t.Fatal("Expect an error but didn't get one") - } - - if actual.SubscriptionId != v.Expected.SubscriptionId { - t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) - } - if actual.ResourceGroup != v.Expected.ResourceGroup { - t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) - } - if actual.ManagedClusterName != v.Expected.ManagedClusterName { - t.Fatalf("Expected %q but got %q for ManagedClusterName", v.Expected.ManagedClusterName, actual.ManagedClusterName) - } - if actual.AgentPoolName != v.Expected.AgentPoolName { - t.Fatalf("Expected %q but got %q for AgentPoolName", v.Expected.AgentPoolName, actual.AgentPoolName) - } - } -} diff --git a/azurerm/internal/services/machinelearning/validate/node_pool_id.go b/azurerm/internal/services/machinelearning/validate/node_pool_id.go deleted file mode 100644 index 10d41b9364a1..000000000000 --- a/azurerm/internal/services/machinelearning/validate/node_pool_id.go +++ /dev/null @@ -1,23 +0,0 @@ -package validate - -// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten - -import ( - "fmt" - - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/machinelearning/parse" -) - -func NodePoolID(input interface{}, key string) (warnings []string, errors []error) { - v, ok := input.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected %q to be a string", key)) - return - } - - if _, err := parse.NodePoolID(v); err != nil { - errors = append(errors, err) - } - - return -} diff --git a/azurerm/internal/services/machinelearning/validate/node_pool_id_test.go b/azurerm/internal/services/machinelearning/validate/node_pool_id_test.go deleted file mode 100644 index f9b3216e64b9..000000000000 --- a/azurerm/internal/services/machinelearning/validate/node_pool_id_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package validate - -// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten - -import "testing" - -func TestNodePoolID(t *testing.T) { - cases := []struct { - Input string - Valid bool - }{ - - { - // empty - Input: "", - Valid: false, - }, - - { - // missing SubscriptionId - Input: "/", - Valid: false, - }, - - { - // missing value for SubscriptionId - Input: "/subscriptions/", - Valid: false, - }, - - { - // missing ResourceGroup - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/", - Valid: false, - }, - - { - // missing value for ResourceGroup - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", - Valid: false, - }, - - { - // missing ManagedClusterName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/", - Valid: false, - }, - - { - // missing value for ManagedClusterName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/", - Valid: false, - }, - - { - // missing AgentPoolName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/", - Valid: false, - }, - - { - // missing value for AgentPoolName - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/", - Valid: false, - }, - - { - // valid - Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1", - Valid: true, - }, - - { - // upper-cased - Input: "/SUBSCRIPTIONS/00000000-0000-0000-0000-000000000000/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CONTAINERSERVICE/MANAGEDCLUSTERS/CLUSTER1/AGENTPOOLS/POOL1", - Valid: false, - }, - } - for _, tc := range cases { - t.Logf("[DEBUG] Testing Value %s", tc.Input) - _, errors := NodePoolID(tc.Input, "test") - valid := len(errors) == 0 - - if tc.Valid != valid { - t.Fatalf("Expected %t but got %t", tc.Valid, valid) - } - } -} From 2dd75e92f67a5db1475af291d2b20b741f02b59c Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Sat, 15 May 2021 19:34:44 +0200 Subject: [PATCH 26/33] Fix FastProd test --- .../machine_learning_inference_cluster_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go index e3774b1f1d2d..636d927511dd 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource_test.go @@ -178,7 +178,7 @@ resource "azurerm_machine_learning_inference_cluster" "import" { } func (r InferenceClusterResource) templateFastProd(data acceptance.TestData) string { - return r.template(data, "SStandard_D3_v2", 3) + return r.template(data, "Standard_D3_v2", 3) } func (r InferenceClusterResource) templateDevTest(data acceptance.TestData) string { return r.template(data, "Standard_DS2_v2", 1) From a715f51667a946c3f88c3412e6b8d711717460ef Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Sat, 15 May 2021 19:35:25 +0200 Subject: [PATCH 27/33] Remove go generate for agentpools --- azurerm/internal/services/machinelearning/resourceids.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/resourceids.go b/azurerm/internal/services/machinelearning/resourceids.go index 30a89ffbbc31..d7f564afee44 100644 --- a/azurerm/internal/services/machinelearning/resourceids.go +++ b/azurerm/internal/services/machinelearning/resourceids.go @@ -2,4 +2,3 @@ package machinelearning //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=InferenceCluster -id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.MachineLearningServices/workspaces/workspace1/computes/cluster1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=KubernetesCluster -id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=NodePool -id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.ContainerService/managedClusters/cluster1/agentPools/pool1 From 650409ef189c2e9301e32f992ceda7ac6d502c7b Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Sat, 15 May 2021 19:41:22 +0200 Subject: [PATCH 28/33] Optimize cluster_purpose values and validation --- .../machine_learning_inference_cluster_resource.go | 8 +++++++- .../r/machine_learning_inference_cluster.html.markdown | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index eae84abcf3b2..3302439dcd59 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -8,6 +8,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/machinelearningservices/mgmt/2020-04-01/machinelearningservices" "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" @@ -69,7 +70,12 @@ func resourceAksInferenceCluster() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "FastProd", + Default: string(machinelearningservices.FastProd), + ValidateFunc: validation.StringInSlice([]string{ + string(machinelearningservices.DenseProd), + string(machinelearningservices.FastProd), + string(machinelearningservices.DenseProd), + }, false), }, "description": { diff --git a/website/docs/r/machine_learning_inference_cluster.html.markdown b/website/docs/r/machine_learning_inference_cluster.html.markdown index 7e61401a6853..9de3e2932a6a 100644 --- a/website/docs/r/machine_learning_inference_cluster.html.markdown +++ b/website/docs/r/machine_learning_inference_cluster.html.markdown @@ -122,7 +122,7 @@ The following arguments are supported: * `machine_learning_workspace_id` - (Required) The ID of the Machine Learning Workspace. Changing this forces a new Machine Learning Inference Cluster to be created. -* `cluster_purpose` - (Optional) The purpose of the Inference Cluster. If used for Development or Testing, use "DevTest" here. Default purpose is "FastProd", which is recommended for production workloads. Changing this forces a new Machine Learning Inference Cluster to be created. +* `cluster_purpose` - (Optional) The purpose of the Inference Cluster. Options are `DevTest`, `DenseProd` and `FastProd`. If used for Development or Testing, use `DevTest` here. Default purpose is `FastProd`, which is recommended for production workloads. Changing this forces a new Machine Learning Inference Cluster to be created. ~> **NOTE:** When creating or attaching a cluster, if the cluster will be used for production (`cluster_purpose = "FastProd"`), then it must contain at least 12 virtual CPUs. The number of virtual CPUs can be calculated by multiplying the number of nodes in the cluster by the number of cores provided by the VM size selected. For example, if you use a VM size of "Standard_D3_v2", which has 4 virtual cores, then you should select 3 or greater as the number of nodes. From 423cf23707ddce95baa9d288b2ad9a661af78d39 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sun, 16 May 2021 22:57:34 +0200 Subject: [PATCH 29/33] update read of ml workspace id --- .../machine_learning_inference_cluster_resource.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 3302439dcd59..c2a92b4145be 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -179,7 +179,6 @@ func resourceAksInferenceClusterCreate(d *schema.ResourceData, meta interface{}) } func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) error { - mlWorkspacesClient := meta.(*clients.Client).MachineLearning.WorkspacesClient mlComputeClient := meta.(*clients.Client).MachineLearning.MachineLearningComputeClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -203,11 +202,9 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e } // Retrieve Machine Learning Workspace ID - mlResp, err := mlWorkspacesClient.Get(ctx, id.ResourceGroup, id.WorkspaceName) - if err != nil { - return err - } - d.Set("machine_learning_workspace_id", mlResp.ID) + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + workspaceId := parse.NewWorkspaceID(subscriptionId, id.ResourceGroup, id.WorkspaceName) + d.Set("machine_learning_workspace_id", workspaceId.ID()) // use ComputeResource to get to AKS Cluster ID and other properties aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(computeResource.Properties) From 5666a7240551cd137013036e54a4b736a42caac9 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Sun, 16 May 2021 22:58:59 +0200 Subject: [PATCH 30/33] make fmt --- .../machine_learning_inference_cluster_resource.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index c2a92b4145be..9c15a6d6a244 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -203,8 +203,8 @@ func resourceAksInferenceClusterRead(d *schema.ResourceData, meta interface{}) e // Retrieve Machine Learning Workspace ID subscriptionId := meta.(*clients.Client).Account.SubscriptionId - workspaceId := parse.NewWorkspaceID(subscriptionId, id.ResourceGroup, id.WorkspaceName) - d.Set("machine_learning_workspace_id", workspaceId.ID()) + workspaceId := parse.NewWorkspaceID(subscriptionId, id.ResourceGroup, id.WorkspaceName) + d.Set("machine_learning_workspace_id", workspaceId.ID()) // use ComputeResource to get to AKS Cluster ID and other properties aksComputeProperties, isAks := (machinelearningservices.BasicCompute).AsAKS(computeResource.Properties) From c704fe3ed69c3bf38fa426db89520a48eb0d38d0 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Tue, 18 May 2021 13:27:46 +0200 Subject: [PATCH 31/33] remove compute type --- .../machine_learning_inference_cluster_resource.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 9c15a6d6a244..deee54d8cdbd 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -260,7 +260,6 @@ func expandAksComputeProperties(aks *containerservice.ManagedCluster, d *schema. ComputeLocation: aks.Location, Description: utils.String(d.Get("description").(string)), ResourceID: aks.ID, - ComputeType: "ComputeTypeAKS1", } } From 79ab9b76b575f833f2b986367019e416e2c7f4d3 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Tue, 18 May 2021 19:19:05 +0200 Subject: [PATCH 32/33] Fix cluster purpose validation: DenseProd -> DevTest --- .../machine_learning_inference_cluster_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index deee54d8cdbd..36a2a32b18e3 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -72,7 +72,7 @@ func resourceAksInferenceCluster() *schema.Resource { ForceNew: true, Default: string(machinelearningservices.FastProd), ValidateFunc: validation.StringInSlice([]string{ - string(machinelearningservices.DenseProd), + string(machinelearningservices.DevTest), string(machinelearningservices.FastProd), string(machinelearningservices.DenseProd), }, false), From 5c22aeddeb6b2fc3bef3755fe2c8a92f8ed2a407 Mon Sep 17 00:00:00 2001 From: Michael Gross Date: Wed, 19 May 2021 22:31:32 +0200 Subject: [PATCH 33/33] upgrade aks api version to 2021-03-01 --- .../machine_learning_inference_cluster_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go index 36a2a32b18e3..52c0100d90f2 100644 --- a/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go +++ b/azurerm/internal/services/machinelearning/machine_learning_inference_cluster_resource.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-02-01/containerservice" + "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2021-03-01/containerservice" "github.com/Azure/azure-sdk-for-go/services/machinelearningservices/mgmt/2020-04-01/machinelearningservices" "github.com/hashicorp/terraform-plugin-sdk/helper/schema"