From 2d1ef69efcb276c8c561b5d2394b1c18827ba577 Mon Sep 17 00:00:00 2001 From: Steph Date: Tue, 17 Sep 2024 12:06:23 +0200 Subject: [PATCH 01/14] create aiservices service package and move ai services resource into it --- internal/provider/services.go | 2 + .../ai_services_resource.go | 102 ++++++++++-------- .../ai_services_resource_test.go | 64 +++++------ internal/services/aiservices/registration.go | 56 ++++++++++ internal/services/cognitive/registration.go | 1 - 5 files changed, 147 insertions(+), 78 deletions(-) rename internal/services/{cognitive => aiservices}/ai_services_resource.go (87%) rename internal/services/{cognitive => aiservices}/ai_services_resource_test.go (91%) create mode 100644 internal/services/aiservices/registration.go diff --git a/internal/provider/services.go b/internal/provider/services.go index a45965bae6c3..794ceb6bf8c7 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c" "github.com/hashicorp/terraform-provider-azurerm/internal/services/advisor" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/aiservices" "github.com/hashicorp/terraform-provider-azurerm/internal/services/analysisservices" "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement" "github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration" @@ -139,6 +140,7 @@ import ( func SupportedTypedServices() []sdk.TypedServiceRegistration { services := []sdk.TypedServiceRegistration{ aadb2c.Registration{}, + aiservices.Registration{}, apimanagement.Registration{}, appconfiguration.Registration{}, applicationinsights.Registration{}, diff --git a/internal/services/cognitive/ai_services_resource.go b/internal/services/aiservices/ai_services_resource.go similarity index 87% rename from internal/services/cognitive/ai_services_resource.go rename to internal/services/aiservices/ai_services_resource.go index 5d62acbc7954..9bea454a3089 100644 --- a/internal/services/cognitive/ai_services_resource.go +++ b/internal/services/aiservices/ai_services_resource.go @@ -1,12 +1,13 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package cognitive +package aiservices import ( "context" "fmt" "log" + "strings" "time" "github.com/hashicorp/go-azure-helpers/lang/pointer" @@ -34,50 +35,61 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/utils" ) -var _ sdk.ResourceWithUpdate = AzureAIServicesResource{} +var _ sdk.ResourceWithUpdate = AIServices{} -var _ sdk.ResourceWithCustomImporter = AzureAIServicesResource{} +var _ sdk.ResourceWithCustomImporter = AIServices{} -type AzureAIServicesResource struct{} +type AIServices struct{} -func (r AzureAIServicesResource) CustomImporter() sdk.ResourceRunFunc { +func (r AIServices) CustomImporter() sdk.ResourceRunFunc { return func(ctx context.Context, metadata sdk.ResourceMetaData) error { - _, err := cognitiveservicesaccounts.ParseAccountID(metadata.ResourceData.Id()) + id, err := cognitiveservicesaccounts.ParseAccountID(metadata.ResourceData.Id()) if err != nil { return err } + + client := metadata.Client.Cognitive.AccountsClient + resp, err := client.AccountsGet(ctx, *id) + if err != nil || resp.Model == nil || resp.Model.Kind == nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if !strings.EqualFold(*resp.Model.Kind, "AIServices") { + return fmt.Errorf("importing %s: specified account is not of kind `AIServices`", id) + } + return nil } } -type AzureAIServicesVirtualNetworkRules struct { +type VirtualNetworkRules struct { SubnetID string `tfschema:"subnet_id"` IgnoreMissingVnetServiceEndpoint bool `tfschema:"ignore_missing_vnet_service_endpoint"` } -type AzureAIServicesNetworkACLs struct { - DefaultAction string `tfschema:"default_action"` - IpRules []string `tfschema:"ip_rules"` - VirtualNetworkRules []AzureAIServicesVirtualNetworkRules `tfschema:"virtual_network_rules"` +type NetworkACLs struct { + DefaultAction string `tfschema:"default_action"` + IpRules []string `tfschema:"ip_rules"` + VirtualNetworkRules []VirtualNetworkRules `tfschema:"virtual_network_rules"` } -type AzureAIServicesCustomerManagedKey struct { +type CustomerManagedKey struct { IdentityClientID string `tfschema:"identity_client_id"` KeyVaultKeyID string `tfschema:"key_vault_key_id"` ManagedHsmKeyID string `tfschema:"managed_hsm_key_id"` } -type AzureAIServicesResourceResourceModel struct { +type AIServicesModel struct { Name string `tfschema:"name"` ResourceGroupName string `tfschema:"resource_group_name"` Location string `tfschema:"location"` SkuName string `tfschema:"sku_name"` CustomSubdomainName string `tfschema:"custom_subdomain_name"` - CustomerManagedKey []AzureAIServicesCustomerManagedKey `tfschema:"customer_managed_key"` + CustomerManagedKey []CustomerManagedKey `tfschema:"customer_managed_key"` Fqdns []string `tfschema:"fqdns"` Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` LocalAuthorizationEnabled bool `tfschema:"local_authentication_enabled"` - NetworkACLs []AzureAIServicesNetworkACLs `tfschema:"network_acls"` + NetworkACLs []NetworkACLs `tfschema:"network_acls"` OutboundNetworkAccessRestricted bool `tfschema:"outbound_network_access_restricted"` PublicNetworkAccess string `tfschema:"public_network_access"` Tags map[string]string `tfschema:"tags"` @@ -86,7 +98,7 @@ type AzureAIServicesResourceResourceModel struct { SecondaryAccessKey string `tfschema:"secondary_access_key"` } -func (AzureAIServicesResource) Arguments() map[string]*pluginsdk.Schema { +func (AIServices) Arguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, @@ -250,7 +262,7 @@ func (AzureAIServicesResource) Arguments() map[string]*pluginsdk.Schema { } } -func (AzureAIServicesResource) Attributes() map[string]*pluginsdk.Schema { +func (AIServices) Attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "endpoint": { Type: pluginsdk.TypeString, @@ -271,19 +283,19 @@ func (AzureAIServicesResource) Attributes() map[string]*pluginsdk.Schema { } } -func (AzureAIServicesResource) ModelObject() interface{} { - return &AzureAIServicesResourceResourceModel{} +func (AIServices) ModelObject() interface{} { + return &AIServicesModel{} } -func (AzureAIServicesResource) ResourceType() string { +func (AIServices) ResourceType() string { return "azurerm_ai_services" } -func (AzureAIServicesResource) Create() sdk.ResourceFunc { +func (AIServices) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 180 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - var model AzureAIServicesResourceResourceModel + var model AIServicesModel if err := metadata.Decode(&model); err != nil { return err } @@ -303,7 +315,7 @@ func (AzureAIServicesResource) Create() sdk.ResourceFunc { return tf.ImportAsExistsError("azurerm_ai_services", id.ID()) } - networkACLs, subnetIds := expandAzureAIServicesNetworkACLs(model.NetworkACLs) + networkACLs, subnetIds := expandNetworkACLs(model.NetworkACLs) // also lock on the Virtual Network ID's since modifications in the networking stack are exclusive virtualNetworkNames := make([]string, 0) @@ -348,7 +360,7 @@ func (AzureAIServicesResource) Create() sdk.ResourceFunc { } // creating with KV HSM takes more time than expected, at least hours in most cases and eventually terminated by service - customerManagedKey, err := expandAzureAIServicesCustomerManagedKey(model.CustomerManagedKey) + customerManagedKey, err := expandCustomerManagedKey(model.CustomerManagedKey) if err != nil { return fmt.Errorf("expanding `customer_managed_key`: %+v", err) } @@ -367,14 +379,14 @@ func (AzureAIServicesResource) Create() sdk.ResourceFunc { } } -func (AzureAIServicesResource) Read() sdk.ResourceFunc { +func (AIServices) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.Cognitive.AccountsClient env := metadata.Client.Account.Environment - state := AzureAIServicesResourceResourceModel{} + state := AIServicesModel{} id, err := cognitiveservicesaccounts.ParseAccountID(metadata.ResourceData.Id()) if err != nil { return err @@ -406,7 +418,7 @@ func (AzureAIServicesResource) Read() sdk.ResourceFunc { if props := model.Properties; props != nil { state.Endpoint = pointer.From(props.Endpoint) state.CustomSubdomainName = pointer.From(props.CustomSubDomainName) - state.NetworkACLs = flattenAzureAIServicesNetworkACLs(props.NetworkAcls) + state.NetworkACLs = flattenNetworkACLs(props.NetworkAcls) state.Fqdns = pointer.From(props.AllowedFqdnList) state.PublicNetworkAccess = string(pointer.From(props.PublicNetworkAccess)) @@ -430,7 +442,7 @@ func (AzureAIServicesResource) Read() sdk.ResourceFunc { } state.LocalAuthorizationEnabled = localAuthEnabled - customerManagedKey, err := flattenAzureAIServicesCustomerManagedKey(props.Encryption, env) + customerManagedKey, err := flattenCustomerManagedKey(props.Encryption, env) if err != nil { return fmt.Errorf("flattening `customer_managed_key`: %+v", err) } @@ -445,13 +457,13 @@ func (AzureAIServicesResource) Read() sdk.ResourceFunc { } } -func (AzureAIServicesResource) Update() sdk.ResourceFunc { +func (AIServices) Update() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 180 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.Cognitive.AccountsClient - var model AzureAIServicesResourceResourceModel + var model AIServicesModel if err := metadata.Decode(&model); err != nil { return err @@ -472,7 +484,7 @@ func (AzureAIServicesResource) Update() sdk.ResourceFunc { props := resp.Model if metadata.ResourceData.HasChange("network_acls") { - networkACLs, subnetIds := expandAzureAIServicesNetworkACLs(model.NetworkACLs) + networkACLs, subnetIds := expandNetworkACLs(model.NetworkACLs) locks.MultipleByName(&subnetIds, network.VirtualNetworkResourceName) defer locks.UnlockMultipleByName(&subnetIds, network.VirtualNetworkResourceName) @@ -525,7 +537,7 @@ func (AzureAIServicesResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("customer_managed_key") { - customerManagedKey, err := expandAzureAIServicesCustomerManagedKey(model.CustomerManagedKey) + customerManagedKey, err := expandCustomerManagedKey(model.CustomerManagedKey) if err != nil { return fmt.Errorf("expanding `customer_managed_key`: %+v", err) } @@ -552,7 +564,7 @@ func (AzureAIServicesResource) Update() sdk.ResourceFunc { } } -func (AzureAIServicesResource) Delete() sdk.ResourceFunc { +func (AIServices) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 180 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -592,11 +604,11 @@ func (AzureAIServicesResource) Delete() sdk.ResourceFunc { } } -func (AzureAIServicesResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { +func (AIServices) IDValidationFunc() pluginsdk.SchemaValidateFunc { return cognitiveservicesaccounts.ValidateAccountID } -func expandAzureAIServicesCustomerManagedKey(input []AzureAIServicesCustomerManagedKey) (*cognitiveservicesaccounts.Encryption, error) { +func expandCustomerManagedKey(input []CustomerManagedKey) (*cognitiveservicesaccounts.Encryption, error) { if len(input) == 0 { return &cognitiveservicesaccounts.Encryption{ KeySource: pointer.To(cognitiveservicesaccounts.KeySourceMicrosoftPointCognitiveServices), @@ -638,15 +650,15 @@ func expandAzureAIServicesCustomerManagedKey(input []AzureAIServicesCustomerMana return encryption, nil } -func flattenAzureAIServicesCustomerManagedKey(input *cognitiveservicesaccounts.Encryption, env environments.Environment) ([]AzureAIServicesCustomerManagedKey, error) { +func flattenCustomerManagedKey(input *cognitiveservicesaccounts.Encryption, env environments.Environment) ([]CustomerManagedKey, error) { if input == nil || *input.KeySource == cognitiveservicesaccounts.KeySourceMicrosoftPointCognitiveServices { - return []AzureAIServicesCustomerManagedKey{}, nil + return []CustomerManagedKey{}, nil } keyName := "" keyVaultURI := "" keyVersion := "" - customerManagerKey := AzureAIServicesCustomerManagedKey{} + customerManagerKey := CustomerManagedKey{} if props := input.KeyVaultProperties; props != nil { if props.KeyName != nil { @@ -690,10 +702,10 @@ func flattenAzureAIServicesCustomerManagedKey(input *cognitiveservicesaccounts.E } } - return []AzureAIServicesCustomerManagedKey{customerManagerKey}, nil + return []CustomerManagedKey{customerManagerKey}, nil } -func expandAzureAIServicesNetworkACLs(input []AzureAIServicesNetworkACLs) (*cognitiveservicesaccounts.NetworkRuleSet, []string) { +func expandNetworkACLs(input []NetworkACLs) (*cognitiveservicesaccounts.NetworkRuleSet, []string) { subnetIds := make([]string, 0) if len(input) == 0 { return nil, subnetIds @@ -731,9 +743,9 @@ func expandAzureAIServicesNetworkACLs(input []AzureAIServicesNetworkACLs) (*cogn return &ruleSet, subnetIds } -func flattenAzureAIServicesNetworkACLs(input *cognitiveservicesaccounts.NetworkRuleSet) []AzureAIServicesNetworkACLs { +func flattenNetworkACLs(input *cognitiveservicesaccounts.NetworkRuleSet) []NetworkACLs { if input == nil { - return []AzureAIServicesNetworkACLs{} + return []NetworkACLs{} } ipRules := make([]string, 0) @@ -743,7 +755,7 @@ func flattenAzureAIServicesNetworkACLs(input *cognitiveservicesaccounts.NetworkR } } - virtualNetworkRules := make([]AzureAIServicesVirtualNetworkRules, 0) + virtualNetworkRules := make([]VirtualNetworkRules, 0) if input.VirtualNetworkRules != nil { for _, v := range *input.VirtualNetworkRules { id := v.Id @@ -752,14 +764,14 @@ func flattenAzureAIServicesNetworkACLs(input *cognitiveservicesaccounts.NetworkR id = subnetId.ID() } - virtualNetworkRules = append(virtualNetworkRules, AzureAIServicesVirtualNetworkRules{ + virtualNetworkRules = append(virtualNetworkRules, VirtualNetworkRules{ SubnetID: id, IgnoreMissingVnetServiceEndpoint: pointer.From(v.IgnoreMissingVnetServiceEndpoint), }) } } - return []AzureAIServicesNetworkACLs{{ + return []NetworkACLs{{ DefaultAction: string(*input.DefaultAction), IpRules: ipRules, VirtualNetworkRules: virtualNetworkRules, diff --git a/internal/services/cognitive/ai_services_resource_test.go b/internal/services/aiservices/ai_services_resource_test.go similarity index 91% rename from internal/services/cognitive/ai_services_resource_test.go rename to internal/services/aiservices/ai_services_resource_test.go index 0c48ca4304ff..4a74b94f37f6 100644 --- a/internal/services/cognitive/ai_services_resource_test.go +++ b/internal/services/aiservices/ai_services_resource_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package cognitive_test +package aiservices_test import ( "context" @@ -17,11 +17,11 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" ) -type AzureAIServicesResource struct{} +type AIServices struct{} -func TestAccCognitiveAzureAIServices_basic(t *testing.T) { +func TestAccAIServices_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -37,9 +37,9 @@ func TestAccCognitiveAzureAIServices_basic(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_requiresImport(t *testing.T) { +func TestAccAIServices_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -55,9 +55,9 @@ func TestAccCognitiveAzureAIServices_requiresImport(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_complete(t *testing.T) { +func TestAccAIServices_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -72,9 +72,9 @@ func TestAccCognitiveAzureAIServices_complete(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_update(t *testing.T) { +func TestAccAIServices_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -97,9 +97,9 @@ func TestAccCognitiveAzureAIServices_update(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_networkACLs(t *testing.T) { +func TestAccAIServices_networkACLs(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -119,9 +119,9 @@ func TestAccCognitiveAzureAIServices_networkACLs(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_identity(t *testing.T) { +func TestAccAIServices_identity(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -159,9 +159,9 @@ func TestAccCognitiveAzureAIServices_identity(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_customerManagedKey_update(t *testing.T) { +func TestAccAIServices_customerManagedKey_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -192,13 +192,13 @@ func TestAccCognitiveAzureAIServices_customerManagedKey_update(t *testing.T) { }) } -func TestAccCognitiveAzureAIServices_KVHsmManagedKey(t *testing.T) { +func TestAccAIServices_KVHsmManagedKey(t *testing.T) { if os.Getenv("ARM_TEST_HSM_KEY") == "" { t.Skip("Skipping as ARM_TEST_HSM_KEY is not specified") return } data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") - r := AzureAIServicesResource{} + r := AIServices{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -213,7 +213,7 @@ func TestAccCognitiveAzureAIServices_KVHsmManagedKey(t *testing.T) { }) } -func (AzureAIServicesResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { +func (AIServices) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := cognitiveservicesaccounts.ParseAccountID(state.ID) if err != nil { return nil, err @@ -227,7 +227,7 @@ func (AzureAIServicesResource) Exists(ctx context.Context, clients *clients.Clie return pointer.To(resp.Model != nil), nil } -func (AzureAIServicesResource) basic(data acceptance.TestData) string { +func (AIServices) basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -247,7 +247,7 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func (AzureAIServicesResource) identitySystemAssigned(data acceptance.TestData) string { +func (AIServices) identitySystemAssigned(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -270,7 +270,7 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func (AzureAIServicesResource) identityUserAssigned(data acceptance.TestData) string { +func (AIServices) identityUserAssigned(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -302,7 +302,7 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } -func (AzureAIServicesResource) identitySystemAssignedUserAssigned(data acceptance.TestData) string { +func (AIServices) identitySystemAssignedUserAssigned(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -334,8 +334,8 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) } -func (AzureAIServicesResource) requiresImport(data acceptance.TestData) string { - template := AzureAIServicesResource{}.basic(data) +func (AIServices) requiresImport(data acceptance.TestData) string { + template := AIServices{}.basic(data) return fmt.Sprintf(` %s @@ -348,7 +348,7 @@ resource "azurerm_ai_services" "import" { `, template) } -func (AzureAIServicesResource) complete(data acceptance.TestData) string { +func (AIServices) complete(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -469,7 +469,7 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomIntOfLength(8)) } -func (r AzureAIServicesResource) networkACLs(data acceptance.TestData) string { +func (r AIServices) networkACLs(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -493,7 +493,7 @@ resource "azurerm_ai_services" "test" { `, r.networkACLsTemplate(data), data.RandomInteger, data.RandomInteger) } -func (r AzureAIServicesResource) networkACLsUpdated(data acceptance.TestData) string { +func (r AIServices) networkACLsUpdated(data acceptance.TestData) string { return fmt.Sprintf(` %s resource "azurerm_ai_services" "test" { @@ -517,7 +517,7 @@ resource "azurerm_ai_services" "test" { `, r.networkACLsTemplate(data), data.RandomInteger, data.RandomInteger) } -func (AzureAIServicesResource) networkACLsTemplate(data acceptance.TestData) string { +func (AIServices) networkACLsTemplate(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -556,7 +556,7 @@ resource "azurerm_subnet" "test_b" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } -func (AzureAIServicesResource) customerManagedKey(data acceptance.TestData) string { +func (AIServices) customerManagedKey(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -642,7 +642,7 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Secondary, data.RandomString, data.RandomString, data.RandomString, data.RandomInteger, data.RandomInteger) } -func (AzureAIServicesResource) customerManagedKeyUpdate(data acceptance.TestData) string { +func (AIServices) customerManagedKeyUpdate(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -723,7 +723,7 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Secondary, data.RandomString, data.RandomString, data.RandomString, data.RandomInteger, data.RandomInteger) } -func (AzureAIServicesResource) kvHsmManagedKey(data acceptance.TestData) string { +func (AIServices) kvHsmManagedKey(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { diff --git a/internal/services/aiservices/registration.go b/internal/services/aiservices/registration.go new file mode 100644 index 000000000000..2dd08ce58d1a --- /dev/null +++ b/internal/services/aiservices/registration.go @@ -0,0 +1,56 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package aiservices + +import ( + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type Registration struct{} + +var ( + _ sdk.TypedServiceRegistration = Registration{} + _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{} +) + +func (r Registration) AssociatedGitHubLabel() string { + return "service/ai-services" +} + +// Name is the name of this Service +func (r Registration) Name() string { + return "AI Services" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "AI Services", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { + return map[string]*pluginsdk.Resource{} +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { + return map[string]*pluginsdk.Resource{} +} + +// DataSources returns a list of Data Sources supported by this Service +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{} +} + +// Resources returns a list of Resources supported by this Service +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + AIServices{}, + AIServicesHub{}, + AIServicesProject{}, + } +} diff --git a/internal/services/cognitive/registration.go b/internal/services/cognitive/registration.go index 5c3cd5d03c5a..e696d00869a1 100644 --- a/internal/services/cognitive/registration.go +++ b/internal/services/cognitive/registration.go @@ -54,7 +54,6 @@ func (r Registration) DataSources() []sdk.DataSource { // Resources returns a list of Resources supported by this Service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ - AzureAIServicesResource{}, CognitiveDeploymentResource{}, } } From e3a0fc49c8e92d4ca4bbbe3ea17026116ab0fb52 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 10:01:11 +0200 Subject: [PATCH 02/14] prevent unnecessary patch when creating ai services account when CMK hasn't been set, also standardize the requiresImport test --- .../aiservices/ai_services_resource.go | 20 ++++++++++--------- .../aiservices/ai_services_resource_test.go | 5 +---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/internal/services/aiservices/ai_services_resource.go b/internal/services/aiservices/ai_services_resource.go index 9bea454a3089..d0462ed6b68b 100644 --- a/internal/services/aiservices/ai_services_resource.go +++ b/internal/services/aiservices/ai_services_resource.go @@ -55,7 +55,7 @@ func (r AIServices) CustomImporter() sdk.ResourceRunFunc { } if !strings.EqualFold(*resp.Model.Kind, "AIServices") { - return fmt.Errorf("importing %s: specified account is not of kind `AIServices`", id) + return fmt.Errorf("importing %s: specified account is not of kind `AIServices`, got `%s`", id, *resp.Model.Kind) } return nil @@ -360,15 +360,17 @@ func (AIServices) Create() sdk.ResourceFunc { } // creating with KV HSM takes more time than expected, at least hours in most cases and eventually terminated by service - customerManagedKey, err := expandCustomerManagedKey(model.CustomerManagedKey) - if err != nil { - return fmt.Errorf("expanding `customer_managed_key`: %+v", err) - } + if len(model.CustomerManagedKey) > 0 { + customerManagedKey, err := expandCustomerManagedKey(model.CustomerManagedKey) + if err != nil { + return fmt.Errorf("expanding `customer_managed_key`: %+v", err) + } - if customerManagedKey != nil { - props.Properties.Encryption = customerManagedKey - if err := client.AccountsUpdateThenPoll(ctx, id, props); err != nil { - return fmt.Errorf("updating %s: %+v", id, err) + if customerManagedKey != nil { + props.Properties.Encryption = customerManagedKey + if err := client.AccountsUpdateThenPoll(ctx, id, props); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } } } diff --git a/internal/services/aiservices/ai_services_resource_test.go b/internal/services/aiservices/ai_services_resource_test.go index 4a74b94f37f6..05cda1bc66eb 100644 --- a/internal/services/aiservices/ai_services_resource_test.go +++ b/internal/services/aiservices/ai_services_resource_test.go @@ -48,10 +48,7 @@ func TestAccAIServices_requiresImport(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - { - Config: r.requiresImport(data), - ExpectError: acceptance.RequiresImportError("azurerm_ai_services"), - }, + data.RequiresImportErrorStep(r.requiresImport), }) } From 9ed6a03565ddded9ec63762111b8f8629e6f1d30 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 14:25:15 +0200 Subject: [PATCH 03/14] add ai services hub resource --- .../aiservices/ai_services_hub_resource.go | 635 ++++++++++++++++++ .../ai_services_hub_resource_test.go | 499 ++++++++++++++ website/docs/r/ai_services_hub.html.markdown | 178 +++++ 3 files changed, 1312 insertions(+) create mode 100644 internal/services/aiservices/ai_services_hub_resource.go create mode 100644 internal/services/aiservices/ai_services_hub_resource_test.go create mode 100644 website/docs/r/ai_services_hub.html.markdown diff --git a/internal/services/aiservices/ai_services_hub_resource.go b/internal/services/aiservices/ai_services_hub_resource.go new file mode 100644 index 000000000000..8a8e9609f675 --- /dev/null +++ b/internal/services/aiservices/ai_services_hub_resource.go @@ -0,0 +1,635 @@ +package aiservices + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" + components "github.com/hashicorp/go-azure-sdk/resource-manager/applicationinsights/2020-02-02/componentsapis" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-06-01-preview/registries" + "github.com/hashicorp/go-azure-sdk/resource-manager/machinelearningservices/2024-04-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + keyvaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + keyvaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/machinelearning/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type AIServicesHub struct{} + +type AIServicesHubModel struct { + Name string `tfschema:"name"` + Location string `tfschema:"location"` + ResourceGroupName string `tfschema:"resource_group_name"` + ApplicationInsightsId string `tfschema:"application_insights_id"` + StorageAccountId string `tfschema:"storage_account_id"` + KeyVaultId string `tfschema:"key_vault_id"` + ContainerRegistryId string `tfschema:"container_registry_id"` + Encryption []Encryption `tfschema:"encryption"` + ManagedNetwork []ManagedNetwork `tfschema:"managed_network"` + PublicNetworkAccess string `tfschema:"public_network_access"` + Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` + PrimaryUserAssignedIdentity string `tfschema:"primary_user_assigned_identity"` + HighBusinessImpactEnabled bool `tfschema:"high_business_impact_enabled"` + ImageBuildComputeName string `tfschema:"image_build_compute_name"` + Description string `tfschema:"description"` + FriendlyName string `tfschema:"friendly_name"` + DiscoveryUrl string `tfschema:"discovery_url"` + WorkspaceId string `tfschema:"workspace_id"` + Tags map[string]interface{} `tfschema:"tags"` +} + +type ManagedNetwork struct { + IsolationMode string `tfschema:"isolation_mode"` +} + +type Encryption struct { + IdentityClientID string `tfschema:"user_assigned_identity_id"` + KeyVaultID string `tfschema:"key_vault_id"` + KeyID string `tfschema:"key_id"` +} + +func (r AIServicesHub) ModelObject() interface{} { + return &AIServicesHubModel{} +} + +func (r AIServicesHub) ResourceType() string { + return "azurerm_ai_services_hub" +} + +func (r AIServicesHub) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return workspaces.ValidateWorkspaceID +} + +func (r AIServicesHub) CustomImporter() sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + client := metadata.Client.MachineLearning.Workspaces + resp, err := client.Get(ctx, *id) + if err != nil || resp.Model == nil || resp.Model.Kind == nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if !strings.EqualFold(*resp.Model.Kind, "Hub") { + return fmt.Errorf("importing %s: specified workspace is not of kind `Hub`, got `%s`", id, *resp.Model.Kind) + } + + return nil + } +} + +var _ sdk.ResourceWithUpdate = AIServicesHub{} + +var _ sdk.ResourceWithCustomImporter = AIServicesHub{} + +func (r AIServicesHub) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.WorkspaceName, + }, + + "location": commonschema.Location(), + + "resource_group_name": commonschema.ResourceGroupName(), + + "key_vault_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: commonids.ValidateKeyVaultID, + }, + + "storage_account_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: commonids.ValidateStorageAccountID, + }, + + "identity": commonschema.SystemAssignedUserAssignedIdentityRequired(), + + "high_business_impact_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + // NOTE: O+C creating a hub that has encryption enabled will set this property to true + Computed: true, + ForceNew: true, + }, + + "encryption": { + Type: pluginsdk.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "key_vault_id": commonschema.ResourceIDReferenceRequired(&commonids.KeyVaultId{}), + "key_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyvaultValidate.NestedItemId, + }, + "user_assigned_identity_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateUserAssignedIdentityID, + // Can be removed when https://github.com/Azure/azure-rest-api-specs/issues/30625 has been fixed + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + }, + }, + + "application_insights_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: components.ValidateComponentID, + }, + + "container_registry_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: registries.ValidateRegistryID, + }, + + "managed_network": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "isolation_mode": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(workspaces.PossibleValuesForIsolationMode(), false), + }, + }, + }, + }, + + "primary_user_assigned_identity": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateUserAssignedIdentityID, + }, + + "public_network_access": { + Type: pluginsdk.TypeString, + Optional: true, + Default: workspaces.PublicNetworkAccessEnabled, + ValidateFunc: validation.StringInSlice(workspaces.PossibleValuesForPublicNetworkAccess(), false), + }, + + "image_build_compute_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "friendly_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tags": commonschema.Tags(), + } +} + +func (r AIServicesHub) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "discovery_url": { + Type: pluginsdk.TypeString, + Computed: true, + }, + + "workspace_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (r AIServicesHub) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 60 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + subscriptionId := metadata.Client.Account.SubscriptionId + + var model AIServicesHubModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding %+v", err) + } + + id := workspaces.NewWorkspaceID(subscriptionId, model.ResourceGroupName, model.Name) + + existing, err := client.Get(ctx, id) + if err != nil { + if !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + if !response.WasNotFound(existing.HttpResponse) { + return tf.ImportAsExistsError("azurerm_ai_services_hub", id.ID()) + } + + storageAccountId, err := commonids.ParseStorageAccountID(model.StorageAccountId) + if err != nil { + return err + } + + keyVaultId, err := commonids.ParseKeyVaultID(model.KeyVaultId) + if err != nil { + return err + } + + expandedIdentity, err := identity.ExpandLegacySystemAndUserAssignedMap(metadata.ResourceData.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + + payload := workspaces.Workspace{ + Name: pointer.To(id.WorkspaceName), + Location: pointer.To(location.Normalize(model.Location)), + Identity: expandedIdentity, + Tags: tags.Expand(model.Tags), + Kind: pointer.To("Hub"), + Properties: &workspaces.WorkspaceProperties{ + KeyVault: pointer.To(keyVaultId.ID()), + PublicNetworkAccess: pointer.To(workspaces.PublicNetworkAccess(model.PublicNetworkAccess)), + StorageAccount: pointer.To(storageAccountId.ID()), + }, + } + + if model.ApplicationInsightsId != "" { + applicationInsightsId, err := components.ParseComponentID(model.ApplicationInsightsId) + if err != nil { + return err + } + payload.Properties.ApplicationInsights = pointer.To(applicationInsightsId.ID()) + } + + if model.ContainerRegistryId != "" { + containerRegistryId, err := registries.ParseRegistryID(model.ContainerRegistryId) + if err != nil { + return err + } + payload.Properties.ContainerRegistry = pointer.To(containerRegistryId.ID()) + } + + if model.Description != "" { + payload.Properties.Description = pointer.To(model.Description) + } + + if model.FriendlyName != "" { + payload.Properties.FriendlyName = pointer.To(model.FriendlyName) + } + + if model.HighBusinessImpactEnabled { + payload.Properties.HbiWorkspace = pointer.To(model.HighBusinessImpactEnabled) + } + + if model.ImageBuildComputeName != "" { + payload.Properties.ImageBuildCompute = pointer.To(model.ImageBuildComputeName) + } + + if model.PrimaryUserAssignedIdentity != "" { + userAssignedId, err := commonids.ParseUserAssignedIdentityID(model.PrimaryUserAssignedIdentity) + if err != nil { + return err + } + payload.Properties.PrimaryUserAssignedIdentity = pointer.To(userAssignedId.ID()) + } + + if len(model.Encryption) > 0 { + encryption := expandEncryption(model.Encryption) + payload.Properties.Encryption = encryption + } + + if len(model.ManagedNetwork) > 0 { + payload.Properties.ManagedNetwork = expandManagedNetwork(model.ManagedNetwork) + } + + if err = client.CreateOrUpdateThenPoll(ctx, id, payload); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r AIServicesHub) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var state AIServicesHubModel + if err := metadata.Decode(&state); err != nil { + return err + } + + existing, err := client.Get(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + if existing.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", id) + } + + payload := existing.Model + + if metadata.ResourceData.HasChange("application_insights_id") { + applicationInsightsId, err := components.ParseComponentID(state.ApplicationInsightsId) + if err != nil { + return err + } + payload.Properties.ApplicationInsights = pointer.To(applicationInsightsId.ID()) + } + + if metadata.ResourceData.HasChange("container_registry_id") { + containerRegistryId, err := registries.ParseRegistryID(state.ContainerRegistryId) + if err != nil { + return err + } + payload.Properties.ContainerRegistry = pointer.To(containerRegistryId.ID()) + } + + if metadata.ResourceData.HasChange("public_network_access") { + payload.Properties.PublicNetworkAccess = pointer.To(workspaces.PublicNetworkAccess(state.PublicNetworkAccess)) + } + + if metadata.ResourceData.HasChange("image_build_compute_name") { + payload.Properties.ImageBuildCompute = pointer.To(state.ImageBuildComputeName) + } + + if metadata.ResourceData.HasChange("description") { + payload.Properties.Description = pointer.To(state.Description) + } + + if metadata.ResourceData.HasChange("friendly_name") { + payload.Properties.FriendlyName = pointer.To(state.FriendlyName) + } + + if metadata.ResourceData.HasChange("identity") { + expandedIdentity, err := identity.ExpandLegacySystemAndUserAssignedMap(metadata.ResourceData.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + payload.Identity = expandedIdentity + } + + if metadata.ResourceData.HasChange("primary_user_assigned_identity") { + userAssignedId, err := commonids.ParseUserAssignedIdentityID(state.PrimaryUserAssignedIdentity) + if err != nil { + return err + } + payload.Properties.PrimaryUserAssignedIdentity = pointer.To(userAssignedId.ID()) + } + + if metadata.ResourceData.HasChange("managed_network") { + payload.Properties.ManagedNetwork = expandManagedNetwork(state.ManagedNetwork) + } + + if metadata.ResourceData.HasChange("tags") { + payload.Tags = tags.Expand(state.Tags) + } + + if err = client.CreateOrUpdateThenPoll(ctx, *id, *payload); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r AIServicesHub) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + hub := AIServicesHubModel{ + Name: id.WorkspaceName, + ResourceGroupName: id.ResourceGroupName, + } + + if model := resp.Model; model != nil { + hub.Location = location.NormalizeNilable(model.Location) + + flattenedIdentity, err := identity.FlattenLegacySystemAndUserAssignedMapToModel(model.Identity) + if err != nil { + return fmt.Errorf("flattening `identity`: %+v", err) + } + + hub.Identity = flattenedIdentity + hub.Tags = tags.Flatten(model.Tags) + + if props := model.Properties; props != nil { + if v := pointer.From(props.ApplicationInsights); v != "" { + applicationInsightsId, err := components.ParseComponentIDInsensitively(v) + if err != nil { + return err + } + hub.ApplicationInsightsId = applicationInsightsId.ID() + } + + if v := pointer.From(props.ContainerRegistry); v != "" { + containerRegistryId, err := registries.ParseRegistryID(v) + if err != nil { + return err + } + hub.ContainerRegistryId = containerRegistryId.ID() + } + + storageAccountId, err := commonids.ParseStorageAccountID(*props.StorageAccount) + if err != nil { + return err + } + hub.StorageAccountId = storageAccountId.ID() + + keyVaultId, err := commonids.ParseKeyVaultID(*props.KeyVault) + if err != nil { + return err + } + hub.KeyVaultId = keyVaultId.ID() + + hub.Description = pointer.From(props.Description) + hub.FriendlyName = pointer.From(props.FriendlyName) + hub.HighBusinessImpactEnabled = pointer.From(props.HbiWorkspace) + hub.ImageBuildComputeName = pointer.From(props.ImageBuildCompute) + hub.PublicNetworkAccess = string(*props.PublicNetworkAccess) + hub.DiscoveryUrl = pointer.From(props.DiscoveryUrl) + hub.WorkspaceId = pointer.From(props.WorkspaceId) + hub.ManagedNetwork = flattenManagedNetwork(props.ManagedNetwork) + + if v := pointer.From(props.PrimaryUserAssignedIdentity); v != "" { + userAssignedId, err := commonids.ParseUserAssignedIdentityID(v) + if err != nil { + return err + } + hub.PrimaryUserAssignedIdentity = userAssignedId.ID() + } + + encryption, err := flattenEncryption(props.Encryption) + if err != nil { + return fmt.Errorf("flattening `encryption`: %+v", err) + } + hub.Encryption = encryption + } + } + + return metadata.Encode(&hub) + }, + } +} + +func (r AIServicesHub) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + opts := workspaces.DefaultDeleteOperationOptions() + + if metadata.Client.Features.MachineLearning.PurgeSoftDeletedWorkspaceOnDestroy { + opts.ForceToPurge = pointer.To(true) + } + + if err := client.DeleteThenPoll(ctx, *id, opts); err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + return nil + }, + } +} + +func expandEncryption(input []Encryption) *workspaces.EncryptionProperty { + encryption := input[0] + out := workspaces.EncryptionProperty{ + Identity: &workspaces.IdentityForCmk{}, + KeyVaultProperties: workspaces.EncryptionKeyVaultProperties{ + KeyVaultArmId: encryption.KeyVaultID, + KeyIdentifier: encryption.KeyID, + }, + Status: workspaces.EncryptionStatusEnabled, + } + + if encryption.IdentityClientID != "" { + out.Identity.UserAssignedIdentity = pointer.To(encryption.IdentityClientID) + } + + return &out +} + +func flattenEncryption(input *workspaces.EncryptionProperty) ([]Encryption, error) { + out := make([]Encryption, 0) + + if input == nil || input.Status != workspaces.EncryptionStatusEnabled { + return out, nil + } + + encryption := Encryption{} + if v := input.KeyVaultProperties.KeyVaultArmId; v != "" { + keyVaultId, err := commonids.ParseKeyVaultID(v) + if err != nil { + return nil, err + } + encryption.KeyVaultID = keyVaultId.ID() + } + if v := input.KeyVaultProperties.KeyIdentifier; v != "" { + keyId, err := keyvaultParse.ParseNestedItemID(v) + if err != nil { + return nil, err + } + encryption.KeyID = keyId.ID() + } + + if input.Identity != nil && input.Identity.UserAssignedIdentity != nil { + userAssignedId, err := commonids.ParseUserAssignedIdentityIDInsensitively(*input.Identity.UserAssignedIdentity) + if err != nil { + return nil, err + } + encryption.IdentityClientID = userAssignedId.ID() + } + // /subscriptions/1a6092a6-137e-4025-9a7c-ef77f76f2c02/resourceGroups/acctestrg-aiservices-240918114038029797/providers/Microsoft.ManagedIdentity/userAssignedIdentities/acctestuai-240918114038029797" + // /subscriptions/1a6092a6-137e-4025-9a7c-ef77f76f2c02/resourceGroups/acctestRG-aiservices-240918114038029797/providers/Microsoft.ManagedIdentity/userAssignedIdentities/acctestuai-240918114038029797" + + return append(out, encryption), nil +} + +func expandManagedNetwork(input []ManagedNetwork) *workspaces.ManagedNetworkSettings { + network := input[0] + + return &workspaces.ManagedNetworkSettings{ + IsolationMode: pointer.To(workspaces.IsolationMode(network.IsolationMode)), + } +} + +func flattenManagedNetwork(input *workspaces.ManagedNetworkSettings) []ManagedNetwork { + out := make([]ManagedNetwork, 0) + if input == nil { + return out + } + + return append(out, ManagedNetwork{ + IsolationMode: string(pointer.From(input.IsolationMode)), + }) +} diff --git a/internal/services/aiservices/ai_services_hub_resource_test.go b/internal/services/aiservices/ai_services_hub_resource_test.go new file mode 100644 index 000000000000..26c70f2893ef --- /dev/null +++ b/internal/services/aiservices/ai_services_hub_resource_test.go @@ -0,0 +1,499 @@ +package aiservices_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/machinelearningservices/2024-04-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type AIServicesHub struct{} + +func TestAccAIServicesHub_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") + r := AIServicesHub{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAIServicesHub_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") + r := AIServicesHub{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccAIServicesHub_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") + r := AIServicesHub{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAIServicesHub_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") + r := AIServicesHub{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAIServicesHub_encryptionWithSystemAssignedId(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") + r := AIServicesHub{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.encryptionWithSystemAssignedId(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAIServicesHub_encryptionWithUserAssignedId(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") + r := AIServicesHub{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.encryptionWithUserAssignedId(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (AIServicesHub) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := workspaces.ParseWorkspaceID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.MachineLearning.Workspaces.Get(ctx, *id) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (r AIServicesHub) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +%s + +resource "azurerm_ai_services_hub" "test" { + name = "acctestaihub-%[2]d" + location = azurerm_ai_services.test.location + resource_group_name = azurerm_resource_group.test.name + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + + identity { + type = "SystemAssigned" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r AIServicesHub) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +%s + +resource "azurerm_application_insights" "test" { + name = "acctestai-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_container_registry" "test" { + name = "testacccr%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + // Premium sku is required when creating a hub with AllowInternetOutbound or AllowOnlyApprovedOutbound isolation mode + sku = "Premium" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestuai-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_ai_services_hub" "test" { + name = "acctestaihub-%[2]d" + location = azurerm_ai_services.test.location + resource_group_name = azurerm_resource_group.test.name + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id, + ] + } + + application_insights_id = azurerm_application_insights.test.id + container_registry_id = azurerm_container_registry.test.id + primary_user_assigned_identity = azurerm_user_assigned_identity.test.id + public_network_access = "Disabled" + image_build_compute_name = "buildtest" + description = "AI Hub created by Terraform" + friendly_name = "AI Hub" + high_business_impact_enabled = true + + managed_network { + isolation_mode = "AllowInternetOutbound" + } + + tags = { + env = "test" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r AIServicesHub) update(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +%s + +resource "azurerm_application_insights" "test" { + name = "acctestai-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_container_registry" "test" { + name = "testacccr%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Premium" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestuai-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_user_assigned_identity" "test2" { + name = "acctestuai2-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_ai_services_hub" "test" { + name = "acctestaihub-%[2]d" + location = azurerm_ai_services.test.location + resource_group_name = azurerm_resource_group.test.name + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id, + azurerm_user_assigned_identity.test2.id + ] + } + + application_insights_id = azurerm_application_insights.test.id + container_registry_id = azurerm_container_registry.test.id + primary_user_assigned_identity = azurerm_user_assigned_identity.test2.id + public_network_access = "Enabled" + image_build_compute_name = "buildtestupdated" + description = "AI Hub for Projects" + friendly_name = "AI Hub for OS models" + high_business_impact_enabled = true + + managed_network { + isolation_mode = "AllowOnlyApprovedOutbound" + } + + tags = { + env = "prod" + model = "regression" + } +} +`, r.template(data), data.RandomInteger) +} + +func (r AIServicesHub) encryptionWithUserAssignedId(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +%s + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestuai-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_key_vault_access_policy" "test-uai-policy" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_user_assigned_identity.test.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + key_permissions = [ + "Get", + "Recover", + "UnwrapKey", + "WrapKey", + ] +} + +resource "azurerm_key_vault_key" "test" { + name = "acckvKey-%[2]d" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] + depends_on = [azurerm_key_vault.test, azurerm_key_vault_access_policy.test] +} + +resource "azurerm_role_assignment" "test_kv" { + scope = azurerm_key_vault.test.id + role_definition_name = "Contributor" + principal_id = azurerm_user_assigned_identity.test.principal_id +} + +resource "azurerm_ai_services_hub" "test" { + name = "acctestaihub-%[2]d" + location = azurerm_ai_services.test.location + resource_group_name = azurerm_resource_group.test.name + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + + primary_user_assigned_identity = azurerm_user_assigned_identity.test.id + + encryption { + user_assigned_identity_id = azurerm_user_assigned_identity.test.id + key_vault_id = azurerm_key_vault.test.id + key_id = azurerm_key_vault_key.test.id + } + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id + ] + } + + depends_on = [ + azurerm_role_assignment.test_kv, + azurerm_key_vault_access_policy.test-uai-policy + ] +} +`, r.template(data), data.RandomInteger) +} + +func (r AIServicesHub) encryptionWithSystemAssignedId(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +%s + +resource "azurerm_key_vault_key" "test" { + name = "acckvkey-%[2]d" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] + depends_on = [azurerm_key_vault.test, azurerm_key_vault_access_policy.test] +} + +resource "azurerm_ai_services_hub" "test" { + name = "acctestaihub-%[2]d" + location = azurerm_ai_services.test.location + resource_group_name = azurerm_resource_group.test.name + storage_account_id = azurerm_storage_account.test.id + key_vault_id = azurerm_key_vault.test.id + + encryption { + key_vault_id = azurerm_key_vault.test.id + key_id = azurerm_key_vault_key.test.id + } + + identity { + type = "SystemAssigned" + } +} +`, r.template(data), data.RandomInteger) +} + +func (AIServicesHub) requiresImport(data acceptance.TestData) string { + template := AIServicesHub{}.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_ai_services_hub" "import" { + name = azurerm_ai_services_hub.test.name + location = azurerm_ai_services_hub.test.location + resource_group_name = azurerm_ai_services_hub.test.resource_group_name + storage_account_id = azurerm_ai_services_hub.test.storage_account_id + key_vault_id = azurerm_ai_services_hub.test.key_vault_id + + identity { + type = "SystemAssigned" + } +} +`, template) +} + +func (AIServicesHub) template(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-aiservices-%[1]d" + location = "%[2]s" +} + +resource "azurerm_key_vault" "test" { + name = "acctestvault%[3]s" + 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 + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_access_policy" "test" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Get", + "Delete", + "Purge", + "GetRotationPolicy", + ] +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa%[3]s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_ai_services" "test" { + name = "acctestaiservices-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "S0" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} diff --git a/website/docs/r/ai_services_hub.html.markdown b/website/docs/r/ai_services_hub.html.markdown new file mode 100644 index 000000000000..96619321f23d --- /dev/null +++ b/website/docs/r/ai_services_hub.html.markdown @@ -0,0 +1,178 @@ +--- +subcategory: "AI Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_ai_services_hub" +description: |- + Manages an AI Services Hub. +--- + +# azurerm_ai_services_hub + +Manages an AI Services Hub. + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example" + location = "westeurope" +} + +resource "azurerm_key_vault" "example" { + name = "examplekv" + 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_key_vault_access_policy" "test" { + key_vault_id = azurerm_key_vault.example.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Get", + "Delete", + "Purge", + "GetRotationPolicy", + ] +} + +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_ai_services" "example" { + name = "exampleaiservices" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku_name = "S0" +} + +resource "azurerm_ai_services_hub" "example" { + name = "exampleaihub" + location = azurerm_ai_services.example.location + resource_group_name = azurerm_resource_group.example.name + storage_account_id = azurerm_storage_account.example.id + key_vault_id = azurerm_key_vault.example.id + + identity { + type = "SystemAssigned" + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this AI Services Hub. Changing this forces a new AI Services Hub to be created. + +* `location` - (Required) The Azure Region where the AI Services Hub should exist. Changing this forces a new AI Services Hub to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the AI Services Hub should exist. Changing this forces a new AI Services Hub to be created. + +* `identity` - (Required) A `identity` block as defined below. + +* `key_vault_id` - (Required) The Key Vault ID that should be used by this AI Services Hub. Changing this forces a new AI Services Hub to be created. + +* `storage_account_id` - (Required) The Storage Account ID that should be used by this AI Services Hub. Changing this forces a new AI Services Hub to be created. + +--- + +* `application_insights_id` - (Optional) The Application Insights ID that should be used by this AI Services Hub. + +* `container_registry_id` - (Optional) The Container Registry ID that should be used by this AI Services Hub. + +* `description` - (Optional) The description of this AI Services Hub. + +* `encryption` - (Optional) An `encryption` block as defined below. Changing this forces a new AI Services Hub to be created. + +* `friendly_name` - (Optional) The display name of this AI Services Hub. + +* `high_business_impact_enabled` - (Optional) Whether High Business Impact (HBI) should be enabled or not. Enabling this setting will reduce diagnostic data collected by the service. Changing this forces a new AI Services Hub to be created. Defaults to `false`. + +-> **Note:** `high_business_impact_enabled` will be enabled by default when creating an AI Services Hub with `encryption` enabled. + +* `image_build_compute_name` - (Optional) The compute name for image build of the AI Services Hub. + +* `managed_network` - (Optional) A `managed_network` block as defined below. + +* `primary_user_assigned_identity` - (Optional) The user assigned identity ID that represents the AI Services Hub identity. This must be set when enabling encryption with a user assigned identity. + +* `public_network_access` - (Optional) Whether public network access for this AI Service Hub should be enabled. Possible values include `Enabled` and `Disabled`. Defaults to `Enabled`. + +* `tags` - (Optional) A mapping of tags which should be assigned to the AI Services Hub. + +--- + +A `encryption` block supports the following: + +* `key_id` - (Required) The Key Vault URI to access the encryption key. + +* `key_vault_id` - (Required) The Key Vault ID where the customer owned encryption key exists. + +* `user_assigned_identity_id` - (Optional) The user assigned identity ID that has access to the encryption key. + +~> **Note:** `user_assigned_identity_id` must be set when`identity.type` is `UserAssigned` in order for the service to find the assigned permissions. + +--- + +A `identity` block supports the following: + +* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this AI Services Hub. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). + +* `identity_ids` - (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this AI Services Hub. + +~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. + +--- + +A `managed_network` block supports the following: + +* `isolation_mode` - (Optional) The isolation mode of the AI Services Hub. Possible values are `Disabled`, `AllowOnlyApprovedOutbound`, and `AllowInternetOutbound`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the AI Services Hub. + +* `discovery_url` - The URL for the discovery service to identify regional endpoints for AI Services Hub services. + +* `workspace_id` - The immutable ID associated with this AI Services Hub. + +--- + +An `identity` block exports the following: + +* `principal_id` - The Principal ID associated with this Managed Service Identity. + +* `tenant_id` - The Tenant ID associated with this Managed Service Identity. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 1 hour) Used when creating the AI Services Hub. +* `read` - (Defaults to 5 minutes) Used when retrieving the AI Services Hub. +* `update` - (Defaults to 30 minutes) Used when updating the AI Services Hub. +* `delete` - (Defaults to 30 minutes) Used when deleting the AI Services Hub. + +## Import + +AI Services Hubs can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_ai_services_hub.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.MachineLearningServices/workspaces/hub1 +``` From c02af6ebb55e445e99177dd80a9d92fad005f596 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 14:25:34 +0200 Subject: [PATCH 04/14] add ai services project resource --- .../ai_services_project_resource.go | 355 ++++++++++++++++++ .../ai_services_project_resource_test.go | 187 +++++++++ .../docs/r/ai_services_project.html.markdown | 146 +++++++ 3 files changed, 688 insertions(+) create mode 100644 internal/services/aiservices/ai_services_project_resource.go create mode 100644 internal/services/aiservices/ai_services_project_resource_test.go create mode 100644 website/docs/r/ai_services_project.html.markdown diff --git a/internal/services/aiservices/ai_services_project_resource.go b/internal/services/aiservices/ai_services_project_resource.go new file mode 100644 index 000000000000..0fdb47292c88 --- /dev/null +++ b/internal/services/aiservices/ai_services_project_resource.go @@ -0,0 +1,355 @@ +package aiservices + +import ( + "context" + "fmt" + "regexp" + "strings" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" + "github.com/hashicorp/go-azure-helpers/resourcemanager/location" + "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" + "github.com/hashicorp/go-azure-sdk/resource-manager/machinelearningservices/2024-04-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type AIServicesProject struct{} + +type AIServicesProjectModel struct { + Name string `tfschema:"name"` + Location string `tfschema:"location"` + AIServicesHubId string `tfschema:"ai_services_hub_id"` + Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` + HighBusinessImpactEnabled bool `tfschema:"high_business_impact_enabled"` + ImageBuildComputeName string `tfschema:"image_build_compute_name"` + Description string `tfschema:"description"` + FriendlyName string `tfschema:"friendly_name"` + ProjectId string `tfschema:"project_id"` + Tags map[string]interface{} `tfschema:"tags"` +} + +func (r AIServicesProject) ModelObject() interface{} { + return &AIServicesProjectModel{} +} + +func (r AIServicesProject) ResourceType() string { + return "azurerm_ai_services_project" +} + +func (r AIServicesProject) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return workspaces.ValidateWorkspaceID +} + +func (r AIServicesProject) CustomImporter() sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + client := metadata.Client.MachineLearning.Workspaces + resp, err := client.Get(ctx, *id) + if err != nil || resp.Model == nil || resp.Model.Kind == nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if !strings.EqualFold(*resp.Model.Kind, "Project") { + return fmt.Errorf("importing %s: specified workspace is not of kind `Project`, got `%s`", id, *resp.Model.Kind) + } + + return nil + } +} + +var _ sdk.ResourceWithUpdate = AIServicesProject{} + +var _ sdk.ResourceWithCustomImporter = AIServicesProject{} + +func (r AIServicesProject) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_-]{2,32}$"), + "AI Services Project name must be 2 - 32 characters long, contain only letters, numbers and hyphens.", + ), + }, + + "location": commonschema.Location(), + + "ai_services_hub_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: workspaces.ValidateWorkspaceID, + }, + + "identity": commonschema.SystemAssignedUserAssignedIdentityOptional(), + + "high_business_impact_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + // NOTE: O+C creating a hub that has encryption enabled with system assigned identity will set this property to true + Computed: true, + ForceNew: true, + }, + + "image_build_compute_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "friendly_name": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tags": commonschema.Tags(), + } +} + +func (r AIServicesProject) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "project_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (r AIServicesProject) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + subscriptionId := metadata.Client.Account.SubscriptionId + + var model AIServicesProjectModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding %+v", err) + } + + hubId, err := workspaces.ParseWorkspaceID(model.AIServicesHubId) + if err != nil { + return err + } + + id := workspaces.NewWorkspaceID(subscriptionId, hubId.ResourceGroupName, model.Name) + + existing, err := client.Get(ctx, id) + if err != nil { + if !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + if !response.WasNotFound(existing.HttpResponse) { + return tf.ImportAsExistsError("azurerm_ai_services_project", id.ID()) + } + + payload := workspaces.Workspace{ + Name: pointer.To(id.WorkspaceName), + Location: pointer.To(location.Normalize(model.Location)), + Tags: tags.Expand(model.Tags), + Kind: pointer.To("Project"), + Properties: &workspaces.WorkspaceProperties{ + HubResourceId: pointer.To(hubId.ID()), + }, + } + + if len(model.Identity) > 0 { + expandedIdentity, err := identity.ExpandLegacySystemAndUserAssignedMap(metadata.ResourceData.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + payload.Identity = expandedIdentity + } + + if model.Description != "" { + payload.Properties.Description = pointer.To(model.Description) + } + + if model.FriendlyName != "" { + payload.Properties.FriendlyName = pointer.To(model.FriendlyName) + } + + if model.HighBusinessImpactEnabled { + payload.Properties.HbiWorkspace = pointer.To(model.HighBusinessImpactEnabled) + } + + if model.ImageBuildComputeName != "" { + payload.Properties.ImageBuildCompute = pointer.To(model.ImageBuildComputeName) + } + + if err = client.CreateOrUpdateThenPoll(ctx, id, payload); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r AIServicesProject) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var state AIServicesProjectModel + if err := metadata.Decode(&state); err != nil { + return err + } + + existing, err := client.Get(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + if existing.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", id) + } + + payload := existing.Model + + // Hubs and Projects share the same API where Projects inherit the KV/Storage/AppInsights/CR/Network/Encryption settings from the Hub + // When updating a Project the API will error when trying to send the inherited settings that get returned when we retrieve the resource for patching in changes + // This is a hack to work around this behaviour and design, so that we can continue to support the use of `ignore_changes` on the resource + payload.Properties.ManagedNetwork = nil + payload.Properties.KeyVault = nil + payload.Properties.StorageAccount = nil + payload.Properties.ContainerRegistry = nil + payload.Properties.ApplicationInsights = nil + payload.Properties.Encryption = nil + + if metadata.ResourceData.HasChange("image_build_compute_name") { + payload.Properties.ImageBuildCompute = pointer.To(state.ImageBuildComputeName) + } + + if metadata.ResourceData.HasChange("description") { + payload.Properties.Description = pointer.To(state.Description) + } + + if metadata.ResourceData.HasChange("friendly_name") { + payload.Properties.FriendlyName = pointer.To(state.FriendlyName) + } + + if metadata.ResourceData.HasChange("identity") { + expandedIdentity, err := identity.ExpandLegacySystemAndUserAssignedMap(metadata.ResourceData.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + payload.Identity = expandedIdentity + } + + if metadata.ResourceData.HasChange("tags") { + payload.Tags = tags.Expand(state.Tags) + } + + if err = client.CreateOrUpdateThenPoll(ctx, *id, *payload); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (r AIServicesProject) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + hub := AIServicesProjectModel{ + Name: id.WorkspaceName, + } + + if model := resp.Model; model != nil { + hub.Location = location.NormalizeNilable(model.Location) + flattenedIdentity, err := identity.FlattenLegacySystemAndUserAssignedMapToModel(model.Identity) + if err != nil { + return fmt.Errorf("flattening `identity`: %+v", err) + } + hub.Identity = flattenedIdentity + + hub.Tags = tags.Flatten(model.Tags) + + if props := model.Properties; props != nil { + if v := pointer.From(props.HubResourceId); v != "" { + hubId, err := workspaces.ParseWorkspaceID(v) + if err != nil { + return err + } + hub.AIServicesHubId = hubId.ID() + } + + hub.Description = pointer.From(props.Description) + hub.FriendlyName = pointer.From(props.FriendlyName) + hub.HighBusinessImpactEnabled = pointer.From(props.HbiWorkspace) + hub.ImageBuildComputeName = pointer.From(props.ImageBuildCompute) + hub.ProjectId = pointer.From(props.WorkspaceId) + } + } + + return metadata.Encode(&hub) + }, + } +} + +func (r AIServicesProject) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.MachineLearning.Workspaces + + id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if err := client.DeleteThenPoll(ctx, *id, workspaces.DefaultDeleteOperationOptions()); err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + return nil + }, + } +} diff --git a/internal/services/aiservices/ai_services_project_resource_test.go b/internal/services/aiservices/ai_services_project_resource_test.go new file mode 100644 index 000000000000..f3dec9f3792e --- /dev/null +++ b/internal/services/aiservices/ai_services_project_resource_test.go @@ -0,0 +1,187 @@ +package aiservices_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/machinelearningservices/2024-04-01/workspaces" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type AIServicesProject struct{} + +func TestAccAIServicesProject_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") + r := AIServicesProject{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAIServicesProject_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") + r := AIServicesProject{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccAIServicesProject_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") + r := AIServicesProject{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAIServicesProject_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") + r := AIServicesProject{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (AIServicesProject) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := workspaces.ParseWorkspaceID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.MachineLearning.Workspaces.Get(ctx, *id) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (r AIServicesProject) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_ai_services_project" "test" { + name = "acctestaip-%[2]d" + location = azurerm_ai_services_hub.test.location + ai_services_hub_id = azurerm_ai_services_hub.test.id + + identity { + type = "SystemAssigned" + } +} +`, AIServicesHub{}.basic(data), data.RandomInteger) +} + +func (r AIServicesProject) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_ai_services_project" "test" { + name = "acctestaip-%[2]d" + location = azurerm_ai_services_hub.test.location + ai_services_hub_id = azurerm_ai_services_hub.test.id + + image_build_compute_name = "projectbuild" + description = "AI Project created by Terraform" + friendly_name = "AI Project" + high_business_impact_enabled = false + + identity { + type = "SystemAssigned" + } + + tags = { + model = "regression" + } +} +`, AIServicesHub{}.complete(data), data.RandomInteger) +} + +func (r AIServicesProject) update(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_user_assigned_identity" "test_project" { + name = "acctestuaip-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_ai_services_project" "test" { + name = "acctestaip-%[2]d" + location = azurerm_ai_services_hub.test.location + ai_services_hub_id = azurerm_ai_services_hub.test.id + + image_build_compute_name = "projectbuildupdate" + description = "AI Project updated by Terraform" + friendly_name = "AI Project for OS models" + high_business_impact_enabled = false + + identity { + type = "SystemAssigned" + } + + tags = { + model = "regression" + env = "test" + } +} +`, AIServicesHub{}.complete(data), data.RandomInteger) +} + +func (AIServicesProject) requiresImport(data acceptance.TestData) string { + template := AIServicesProject{}.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_ai_services_project" "import" { + name = azurerm_ai_services_project.test.name + location = azurerm_ai_services_project.test.location + ai_services_hub_id = azurerm_ai_services_project.test.ai_services_hub_id + + identity { + type = "SystemAssigned" + } +} +`, template) +} diff --git a/website/docs/r/ai_services_project.html.markdown b/website/docs/r/ai_services_project.html.markdown new file mode 100644 index 000000000000..5bff3f0aa796 --- /dev/null +++ b/website/docs/r/ai_services_project.html.markdown @@ -0,0 +1,146 @@ +--- +subcategory: "AI Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_ai_services_project" +description: |- + Manages an AI Services Project. +--- + +# azurerm_ai_services_project + +Manages an AI Services Project. + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example" + location = "westeurope" +} + +resource "azurerm_key_vault" "example" { + name = "examplekv" + 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_key_vault_access_policy" "test" { + key_vault_id = azurerm_key_vault.example.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Get", + "Delete", + "Purge", + "GetRotationPolicy", + ] +} + +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_ai_services" "example" { + name = "exampleaiservices" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku_name = "S0" +} + +resource "azurerm_ai_services_hub" "example" { + name = "exampleaihub" + location = azurerm_ai_services.example.location + resource_group_name = azurerm_resource_group.example.name + storage_account_id = azurerm_storage_account.example.id + key_vault_id = azurerm_key_vault.example.id + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_ai_services_project" "example" { + name = "example" + location = azurerm_ai_services_hub.example.location + ai_services_hub_id = azurerm_ai_services_hub.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this AI Services Project. Changing this forces a new AI Services Project to be created. + +* `location` - (Required) The Azure Region where the AI Services Project should exist. Changing this forces a new AI Services Project to be created. + +* `ai_services_hub_id` - (Required) The AI Services Hub ID under which this Project should be created. Changing this forces a new AI Services Project to be created. + +--- + +* `description` - (Optional) The description of this AI Services Project. + +* `friendly_name` - (Optional) The display name of this AI Services Project. + +* `high_business_impact_enabled` - (Optional) Whether High Business Impact (HBI) should be enabled or not. Enabling this setting will reduce diagnostic data collected by the service. Changing this forces a new AI Services Project to be created. Defaults to `false`. + +* `identity` - (Optional) A `identity` block as defined below. + +* `image_build_compute_name` - (Optional) The compute name for image build of the AI Services Project. + +* `tags` - (Optional) A mapping of tags which should be assigned to the AI Services Project. + +--- + +A `identity` block supports the following: + +* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this AI Services Project. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). + +* `identity_ids` - (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this AI Services Project. + +~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the AI Services Project. + +* `project_id` - The immutable project ID associated with this AI Services Project. + +--- + +An `identity` block exports the following: + +* `principal_id` - The Principal ID associated with this Managed Service Identity. + +* `tenant_id` - The Tenant ID associated with this Managed Service Identity. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 hour) Used when creating the AI Services Project. +* `read` - (Defaults to 5 minutes) Used when retrieving the AI Services Project. +* `update` - (Defaults to 30 minutes) Used when updating the AI Services Project. +* `delete` - (Defaults to 30 minutes) Used when deleting the AI Services Project. + +## Import + +AI Services Projects can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_ai_services_project.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.MachineLearningServices/workspaces/project1 +``` From e1a2b7ff6a275012b18c76f4aafa8586abcec6d2 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 14:25:57 +0200 Subject: [PATCH 05/14] change category for ai services account documentation --- website/docs/r/ai_services.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/ai_services.html.markdown b/website/docs/r/ai_services.html.markdown index 9133364e0008..e490d09277a6 100644 --- a/website/docs/r/ai_services.html.markdown +++ b/website/docs/r/ai_services.html.markdown @@ -1,5 +1,5 @@ --- -subcategory: "Cognitive Services" +subcategory: "AI Services" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_ai_services" description: |- @@ -8,7 +8,7 @@ description: |- # azurerm_ai_services -Manages an AI Services account. +Manages an AI Services Account. ## Example Usage From bcbeb16bc86c73d121bb6cc1f81d96c0a6c38e69 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 14:40:56 +0200 Subject: [PATCH 06/14] add AI services subcategory --- internal/services/aiservices/ai_services_project_resource.go | 2 +- website/allowed-subcategories | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/services/aiservices/ai_services_project_resource.go b/internal/services/aiservices/ai_services_project_resource.go index 0fdb47292c88..ed6260c34375 100644 --- a/internal/services/aiservices/ai_services_project_resource.go +++ b/internal/services/aiservices/ai_services_project_resource.go @@ -98,7 +98,7 @@ func (r AIServicesProject) Arguments() map[string]*pluginsdk.Schema { "high_business_impact_enabled": { Type: pluginsdk.TypeBool, Optional: true, - // NOTE: O+C creating a hub that has encryption enabled with system assigned identity will set this property to true + // NOTE: O+C creating a project that has encryption enabled with system assigned identity will set this property to true Computed: true, ForceNew: true, }, diff --git a/website/allowed-subcategories b/website/allowed-subcategories index 01e0bbb4265f..f650ae0f36a5 100644 --- a/website/allowed-subcategories +++ b/website/allowed-subcategories @@ -1,5 +1,6 @@ AAD B2C +AI Services API Management Active Directory Domain Services Advisor From 361acb1d42a9f5a5fd24c2d428008214d1bed0bb Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 14:45:26 +0200 Subject: [PATCH 07/14] terrafmt --- internal/services/aiservices/ai_services_hub_resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/aiservices/ai_services_hub_resource_test.go b/internal/services/aiservices/ai_services_hub_resource_test.go index 26c70f2893ef..63e3066d6934 100644 --- a/internal/services/aiservices/ai_services_hub_resource_test.go +++ b/internal/services/aiservices/ai_services_hub_resource_test.go @@ -440,7 +440,7 @@ resource "azurerm_ai_services_hub" "import" { key_vault_id = azurerm_ai_services_hub.test.key_vault_id identity { - type = "SystemAssigned" + type = "SystemAssigned" } } `, template) From 04040dbe2791f924f8ab547ccb768cc1488eab96 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 18 Sep 2024 14:58:00 +0200 Subject: [PATCH 08/14] make generate --- .github/labeler-issue-triage.yml | 5 ++++- .github/labeler-pull-request-triage.yml | 5 +++++ .teamcity/components/generated/services.kt | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index ba97f5f93250..417612db6d33 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -17,6 +17,9 @@ service/aadb2c: service/advisor: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_advisor_((.|\n)*)###' +service/ai-services: + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_ai_services((.|\n)*)###' + service/analysis: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_analysis_services_server((.|\n)*)###' @@ -66,7 +69,7 @@ service/cdn: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_cdn_((.|\n)*)###' service/cognitive-services: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(ai_services|cognitive_)((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_cognitive_((.|\n)*)###' service/communication: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(communication_service|email_communication_service|gallery_application|orchestrated_virtual_machine_scale_set\W+|restore_point_collection|virtual_machine_gallery_application_assignment\W+|virtual_machine_implicit_data_disk_from_source\W+|virtual_machine_restore_point\W+|virtual_machine_restore_point_collection\W+|virtual_machine_run_command\W+)((.|\n)*)###' diff --git a/.github/labeler-pull-request-triage.yml b/.github/labeler-pull-request-triage.yml index 154f12cb7ac8..a44c954ced6e 100644 --- a/.github/labeler-pull-request-triage.yml +++ b/.github/labeler-pull-request-triage.yml @@ -27,6 +27,11 @@ service/advisor: - any-glob-to-any-file: - internal/services/advisor/**/* +service/ai-services: +- changed-files: + - any-glob-to-any-file: + - internal/services/aiservices/**/* + service/analysis: - changed-files: - any-glob-to-any-file: diff --git a/.teamcity/components/generated/services.kt b/.teamcity/components/generated/services.kt index e6d7678a75f5..dc84eb4f580c 100644 --- a/.teamcity/components/generated/services.kt +++ b/.teamcity/components/generated/services.kt @@ -4,6 +4,7 @@ // to re-generate this file, run 'make generate' in the root of the repository var services = mapOf( "aadb2c" to "AAD B2C", + "aiservices" to "AI Services", "apimanagement" to "API Management", "advisor" to "Advisor", "analysisservices" to "Analysis Services", From 33d6d86a767c2bd7ab6ca5bcdce37d90f2293f43 Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 19 Sep 2024 11:43:34 +0200 Subject: [PATCH 09/14] remove debug comment --- internal/services/aiservices/ai_services_hub_resource.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/services/aiservices/ai_services_hub_resource.go b/internal/services/aiservices/ai_services_hub_resource.go index 8a8e9609f675..c08dd923bf08 100644 --- a/internal/services/aiservices/ai_services_hub_resource.go +++ b/internal/services/aiservices/ai_services_hub_resource.go @@ -609,8 +609,6 @@ func flattenEncryption(input *workspaces.EncryptionProperty) ([]Encryption, erro } encryption.IdentityClientID = userAssignedId.ID() } - // /subscriptions/1a6092a6-137e-4025-9a7c-ef77f76f2c02/resourceGroups/acctestrg-aiservices-240918114038029797/providers/Microsoft.ManagedIdentity/userAssignedIdentities/acctestuai-240918114038029797" - // /subscriptions/1a6092a6-137e-4025-9a7c-ef77f76f2c02/resourceGroups/acctestRG-aiservices-240918114038029797/providers/Microsoft.ManagedIdentity/userAssignedIdentities/acctestuai-240918114038029797" return append(out, encryption), nil } From f2a02d29bdead5b7b99f11526f22403f0a25d311 Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 28 Nov 2024 11:32:28 +0100 Subject: [PATCH 10/14] rename resources to ai foundry --- internal/provider/services.go | 2 - internal/services/aiservices/registration.go | 56 ---------------- .../ai_services_resource.go | 2 +- .../ai_services_resource_test.go | 2 +- internal/services/cognitive/registration.go | 1 + .../ai_foundry_project_resource.go} | 40 +++++------ .../ai_foundry_project_resource_test.go} | 66 +++++++++---------- .../ai_foundry_resource.go} | 44 ++++++------- .../ai_foundry_resource_test.go} | 48 +++++++------- .../services/machinelearning/registration.go | 2 + 10 files changed, 104 insertions(+), 159 deletions(-) delete mode 100644 internal/services/aiservices/registration.go rename internal/services/{aiservices => cognitive}/ai_services_resource.go (99%) rename internal/services/{aiservices => cognitive}/ai_services_resource_test.go (99%) rename internal/services/{aiservices/ai_services_project_resource.go => machinelearning/ai_foundry_project_resource.go} (91%) rename internal/services/{aiservices/ai_services_project_resource_test.go => machinelearning/ai_foundry_project_resource_test.go} (62%) rename internal/services/{aiservices/ai_services_hub_resource.go => machinelearning/ai_foundry_resource.go} (95%) rename internal/services/{aiservices/ai_services_hub_resource_test.go => machinelearning/ai_foundry_resource_test.go} (89%) diff --git a/internal/provider/services.go b/internal/provider/services.go index 794ceb6bf8c7..a45965bae6c3 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/aadb2c" "github.com/hashicorp/terraform-provider-azurerm/internal/services/advisor" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/aiservices" "github.com/hashicorp/terraform-provider-azurerm/internal/services/analysisservices" "github.com/hashicorp/terraform-provider-azurerm/internal/services/apimanagement" "github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration" @@ -140,7 +139,6 @@ import ( func SupportedTypedServices() []sdk.TypedServiceRegistration { services := []sdk.TypedServiceRegistration{ aadb2c.Registration{}, - aiservices.Registration{}, apimanagement.Registration{}, appconfiguration.Registration{}, applicationinsights.Registration{}, diff --git a/internal/services/aiservices/registration.go b/internal/services/aiservices/registration.go deleted file mode 100644 index 2dd08ce58d1a..000000000000 --- a/internal/services/aiservices/registration.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package aiservices - -import ( - "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" - "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" -) - -type Registration struct{} - -var ( - _ sdk.TypedServiceRegistration = Registration{} - _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{} -) - -func (r Registration) AssociatedGitHubLabel() string { - return "service/ai-services" -} - -// Name is the name of this Service -func (r Registration) Name() string { - return "AI Services" -} - -// WebsiteCategories returns a list of categories which can be used for the sidebar -func (r Registration) WebsiteCategories() []string { - return []string{ - "AI Services", - } -} - -// SupportedDataSources returns the supported Data Sources supported by this Service -func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { - return map[string]*pluginsdk.Resource{} -} - -// SupportedResources returns the supported Resources supported by this Service -func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { - return map[string]*pluginsdk.Resource{} -} - -// DataSources returns a list of Data Sources supported by this Service -func (r Registration) DataSources() []sdk.DataSource { - return []sdk.DataSource{} -} - -// Resources returns a list of Resources supported by this Service -func (r Registration) Resources() []sdk.Resource { - return []sdk.Resource{ - AIServices{}, - AIServicesHub{}, - AIServicesProject{}, - } -} diff --git a/internal/services/aiservices/ai_services_resource.go b/internal/services/cognitive/ai_services_resource.go similarity index 99% rename from internal/services/aiservices/ai_services_resource.go rename to internal/services/cognitive/ai_services_resource.go index d0462ed6b68b..455e4cf4e34d 100644 --- a/internal/services/aiservices/ai_services_resource.go +++ b/internal/services/cognitive/ai_services_resource.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package aiservices +package cognitive import ( "context" diff --git a/internal/services/aiservices/ai_services_resource_test.go b/internal/services/cognitive/ai_services_resource_test.go similarity index 99% rename from internal/services/aiservices/ai_services_resource_test.go rename to internal/services/cognitive/ai_services_resource_test.go index 05cda1bc66eb..86a6811266b5 100644 --- a/internal/services/aiservices/ai_services_resource_test.go +++ b/internal/services/cognitive/ai_services_resource_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package aiservices_test +package cognitive_test import ( "context" diff --git a/internal/services/cognitive/registration.go b/internal/services/cognitive/registration.go index e696d00869a1..31835821035f 100644 --- a/internal/services/cognitive/registration.go +++ b/internal/services/cognitive/registration.go @@ -54,6 +54,7 @@ func (r Registration) DataSources() []sdk.DataSource { // Resources returns a list of Resources supported by this Service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ + AIServices{}, CognitiveDeploymentResource{}, } } diff --git a/internal/services/aiservices/ai_services_project_resource.go b/internal/services/machinelearning/ai_foundry_project_resource.go similarity index 91% rename from internal/services/aiservices/ai_services_project_resource.go rename to internal/services/machinelearning/ai_foundry_project_resource.go index ed6260c34375..f649fc86d123 100644 --- a/internal/services/aiservices/ai_services_project_resource.go +++ b/internal/services/machinelearning/ai_foundry_project_resource.go @@ -1,4 +1,4 @@ -package aiservices +package machinelearning import ( "context" @@ -20,9 +20,9 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" ) -type AIServicesProject struct{} +type AIFoundryProject struct{} -type AIServicesProjectModel struct { +type AIFoundryProjectModel struct { Name string `tfschema:"name"` Location string `tfschema:"location"` AIServicesHubId string `tfschema:"ai_services_hub_id"` @@ -35,19 +35,19 @@ type AIServicesProjectModel struct { Tags map[string]interface{} `tfschema:"tags"` } -func (r AIServicesProject) ModelObject() interface{} { - return &AIServicesProjectModel{} +func (r AIFoundryProject) ModelObject() interface{} { + return &AIFoundryProjectModel{} } -func (r AIServicesProject) ResourceType() string { - return "azurerm_ai_services_project" +func (r AIFoundryProject) ResourceType() string { + return "azurerm_ai_foundry_project" } -func (r AIServicesProject) IDValidationFunc() pluginsdk.SchemaValidateFunc { +func (r AIFoundryProject) IDValidationFunc() pluginsdk.SchemaValidateFunc { return workspaces.ValidateWorkspaceID } -func (r AIServicesProject) CustomImporter() sdk.ResourceRunFunc { +func (r AIFoundryProject) CustomImporter() sdk.ResourceRunFunc { return func(ctx context.Context, metadata sdk.ResourceMetaData) error { id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) if err != nil { @@ -68,11 +68,11 @@ func (r AIServicesProject) CustomImporter() sdk.ResourceRunFunc { } } -var _ sdk.ResourceWithUpdate = AIServicesProject{} +var _ sdk.ResourceWithUpdate = AIFoundryProject{} -var _ sdk.ResourceWithCustomImporter = AIServicesProject{} +var _ sdk.ResourceWithCustomImporter = AIFoundryProject{} -func (r AIServicesProject) Arguments() map[string]*pluginsdk.Schema { +func (r AIFoundryProject) Arguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, @@ -125,7 +125,7 @@ func (r AIServicesProject) Arguments() map[string]*pluginsdk.Schema { } } -func (r AIServicesProject) Attributes() map[string]*pluginsdk.Schema { +func (r AIFoundryProject) Attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "project_id": { Type: pluginsdk.TypeString, @@ -134,14 +134,14 @@ func (r AIServicesProject) Attributes() map[string]*pluginsdk.Schema { } } -func (r AIServicesProject) Create() sdk.ResourceFunc { +func (r AIFoundryProject) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.MachineLearning.Workspaces subscriptionId := metadata.Client.Account.SubscriptionId - var model AIServicesProjectModel + var model AIFoundryProjectModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding %+v", err) } @@ -207,7 +207,7 @@ func (r AIServicesProject) Create() sdk.ResourceFunc { } } -func (r AIServicesProject) Update() sdk.ResourceFunc { +func (r AIFoundryProject) Update() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -218,7 +218,7 @@ func (r AIServicesProject) Update() sdk.ResourceFunc { return err } - var state AIServicesProjectModel + var state AIFoundryProjectModel if err := metadata.Decode(&state); err != nil { return err } @@ -279,7 +279,7 @@ func (r AIServicesProject) Update() sdk.ResourceFunc { } } -func (r AIServicesProject) Read() sdk.ResourceFunc { +func (r AIFoundryProject) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -298,7 +298,7 @@ func (r AIServicesProject) Read() sdk.ResourceFunc { return fmt.Errorf("retrieving %s: %+v", *id, err) } - hub := AIServicesProjectModel{ + hub := AIFoundryProjectModel{ Name: id.WorkspaceName, } @@ -334,7 +334,7 @@ func (r AIServicesProject) Read() sdk.ResourceFunc { } } -func (r AIServicesProject) Delete() sdk.ResourceFunc { +func (r AIFoundryProject) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { diff --git a/internal/services/aiservices/ai_services_project_resource_test.go b/internal/services/machinelearning/ai_foundry_project_resource_test.go similarity index 62% rename from internal/services/aiservices/ai_services_project_resource_test.go rename to internal/services/machinelearning/ai_foundry_project_resource_test.go index f3dec9f3792e..b08a42ff3d29 100644 --- a/internal/services/aiservices/ai_services_project_resource_test.go +++ b/internal/services/machinelearning/ai_foundry_project_resource_test.go @@ -1,4 +1,4 @@ -package aiservices_test +package machinelearning_test import ( "context" @@ -13,11 +13,11 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" ) -type AIServicesProject struct{} +type AIFoundryProject struct{} -func TestAccAIServicesProject_basic(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") - r := AIServicesProject{} +func TestAccAIFoundryProject_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry_project", "test") + r := AIFoundryProject{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -30,9 +30,9 @@ func TestAccAIServicesProject_basic(t *testing.T) { }) } -func TestAccAIServicesProject_requiresImport(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") - r := AIServicesProject{} +func TestAccAIFoundryProject_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry_project", "test") + r := AIFoundryProject{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -45,9 +45,9 @@ func TestAccAIServicesProject_requiresImport(t *testing.T) { }) } -func TestAccAIServicesProject_complete(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") - r := AIServicesProject{} +func TestAccAIFoundryProject_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry_project", "test") + r := AIFoundryProject{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -60,9 +60,9 @@ func TestAccAIServicesProject_complete(t *testing.T) { }) } -func TestAccAIServicesProject_update(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_project", "test") - r := AIServicesProject{} +func TestAccAIFoundryProject_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry_project", "test") + r := AIFoundryProject{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -82,7 +82,7 @@ func TestAccAIServicesProject_update(t *testing.T) { }) } -func (AIServicesProject) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { +func (AIFoundryProject) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := workspaces.ParseWorkspaceID(state.ID) if err != nil { return nil, err @@ -96,14 +96,14 @@ func (AIServicesProject) Exists(ctx context.Context, clients *clients.Client, st return pointer.To(resp.Model != nil), nil } -func (r AIServicesProject) basic(data acceptance.TestData) string { +func (r AIFoundryProject) basic(data acceptance.TestData) string { return fmt.Sprintf(` %s -resource "azurerm_ai_services_project" "test" { +resource "azurerm_ai_foundry_project" "test" { name = "acctestaip-%[2]d" - location = azurerm_ai_services_hub.test.location - ai_services_hub_id = azurerm_ai_services_hub.test.id + location = azurerm_ai_foundry.test.location + ai_services_hub_id = azurerm_ai_foundry.test.id identity { type = "SystemAssigned" @@ -112,14 +112,14 @@ resource "azurerm_ai_services_project" "test" { `, AIServicesHub{}.basic(data), data.RandomInteger) } -func (r AIServicesProject) complete(data acceptance.TestData) string { +func (r AIFoundryProject) complete(data acceptance.TestData) string { return fmt.Sprintf(` %s -resource "azurerm_ai_services_project" "test" { +resource "azurerm_ai_foundry_project" "test" { name = "acctestaip-%[2]d" - location = azurerm_ai_services_hub.test.location - ai_services_hub_id = azurerm_ai_services_hub.test.id + location = azurerm_ai_foundry.test.location + ai_services_hub_id = azurerm_ai_foundry.test.id image_build_compute_name = "projectbuild" description = "AI Project created by Terraform" @@ -137,7 +137,7 @@ resource "azurerm_ai_services_project" "test" { `, AIServicesHub{}.complete(data), data.RandomInteger) } -func (r AIServicesProject) update(data acceptance.TestData) string { +func (r AIFoundryProject) update(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -147,10 +147,10 @@ resource "azurerm_user_assigned_identity" "test_project" { location = azurerm_resource_group.test.location } -resource "azurerm_ai_services_project" "test" { +resource "azurerm_ai_foundry_project" "test" { name = "acctestaip-%[2]d" - location = azurerm_ai_services_hub.test.location - ai_services_hub_id = azurerm_ai_services_hub.test.id + location = azurerm_ai_foundry.test.location + ai_services_hub_id = azurerm_ai_foundry.test.id image_build_compute_name = "projectbuildupdate" description = "AI Project updated by Terraform" @@ -169,15 +169,15 @@ resource "azurerm_ai_services_project" "test" { `, AIServicesHub{}.complete(data), data.RandomInteger) } -func (AIServicesProject) requiresImport(data acceptance.TestData) string { - template := AIServicesProject{}.basic(data) +func (AIFoundryProject) requiresImport(data acceptance.TestData) string { + template := AIFoundryProject{}.basic(data) return fmt.Sprintf(` %s -resource "azurerm_ai_services_project" "import" { - name = azurerm_ai_services_project.test.name - location = azurerm_ai_services_project.test.location - ai_services_hub_id = azurerm_ai_services_project.test.ai_services_hub_id +resource "azurerm_ai_foundry_project" "import" { + name = azurerm_ai_foundry_project.test.name + location = azurerm_ai_foundry_project.test.location + ai_services_hub_id = azurerm_ai_foundry_project.test.ai_services_hub_id identity { type = "SystemAssigned" diff --git a/internal/services/aiservices/ai_services_hub_resource.go b/internal/services/machinelearning/ai_foundry_resource.go similarity index 95% rename from internal/services/aiservices/ai_services_hub_resource.go rename to internal/services/machinelearning/ai_foundry_resource.go index c08dd923bf08..dcc24daebfbd 100644 --- a/internal/services/aiservices/ai_services_hub_resource.go +++ b/internal/services/machinelearning/ai_foundry_resource.go @@ -1,4 +1,4 @@ -package aiservices +package machinelearning import ( "context" @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/go-azure-helpers/resourcemanager/location" "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" components "github.com/hashicorp/go-azure-sdk/resource-manager/applicationinsights/2020-02-02/componentsapis" - "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-06-01-preview/registries" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-11-01-preview/registries" "github.com/hashicorp/go-azure-sdk/resource-manager/machinelearningservices/2024-04-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" @@ -26,9 +26,9 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" ) -type AIServicesHub struct{} +type AIFoundry struct{} -type AIServicesHubModel struct { +type AIFoundryModel struct { Name string `tfschema:"name"` Location string `tfschema:"location"` ResourceGroupName string `tfschema:"resource_group_name"` @@ -60,19 +60,19 @@ type Encryption struct { KeyID string `tfschema:"key_id"` } -func (r AIServicesHub) ModelObject() interface{} { - return &AIServicesHubModel{} +func (r AIFoundry) ModelObject() interface{} { + return &AIFoundryModel{} } -func (r AIServicesHub) ResourceType() string { - return "azurerm_ai_services_hub" +func (r AIFoundry) ResourceType() string { + return "azurerm_ai_foundry" } -func (r AIServicesHub) IDValidationFunc() pluginsdk.SchemaValidateFunc { +func (r AIFoundry) IDValidationFunc() pluginsdk.SchemaValidateFunc { return workspaces.ValidateWorkspaceID } -func (r AIServicesHub) CustomImporter() sdk.ResourceRunFunc { +func (r AIFoundry) CustomImporter() sdk.ResourceRunFunc { return func(ctx context.Context, metadata sdk.ResourceMetaData) error { id, err := workspaces.ParseWorkspaceID(metadata.ResourceData.Id()) if err != nil { @@ -93,11 +93,11 @@ func (r AIServicesHub) CustomImporter() sdk.ResourceRunFunc { } } -var _ sdk.ResourceWithUpdate = AIServicesHub{} +var _ sdk.ResourceWithUpdate = AIFoundry{} -var _ sdk.ResourceWithCustomImporter = AIServicesHub{} +var _ sdk.ResourceWithCustomImporter = AIFoundry{} -func (r AIServicesHub) Arguments() map[string]*pluginsdk.Schema { +func (r AIFoundry) Arguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, @@ -222,7 +222,7 @@ func (r AIServicesHub) Arguments() map[string]*pluginsdk.Schema { } } -func (r AIServicesHub) Attributes() map[string]*pluginsdk.Schema { +func (r AIFoundry) Attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "discovery_url": { Type: pluginsdk.TypeString, @@ -236,14 +236,14 @@ func (r AIServicesHub) Attributes() map[string]*pluginsdk.Schema { } } -func (r AIServicesHub) Create() sdk.ResourceFunc { +func (r AIFoundry) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 60 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.MachineLearning.Workspaces subscriptionId := metadata.Client.Account.SubscriptionId - var model AIServicesHubModel + var model AIFoundryModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding %+v", err) } @@ -347,7 +347,7 @@ func (r AIServicesHub) Create() sdk.ResourceFunc { } } -func (r AIServicesHub) Update() sdk.ResourceFunc { +func (r AIFoundry) Update() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -358,7 +358,7 @@ func (r AIServicesHub) Update() sdk.ResourceFunc { return err } - var state AIServicesHubModel + var state AIFoundryModel if err := metadata.Decode(&state); err != nil { return err } @@ -441,7 +441,7 @@ func (r AIServicesHub) Update() sdk.ResourceFunc { } } -func (r AIServicesHub) Read() sdk.ResourceFunc { +func (r AIFoundry) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -460,7 +460,7 @@ func (r AIServicesHub) Read() sdk.ResourceFunc { return fmt.Errorf("retrieving %s: %+v", *id, err) } - hub := AIServicesHubModel{ + hub := AIFoundryModel{ Name: id.WorkspaceName, ResourceGroupName: id.ResourceGroupName, } @@ -510,7 +510,7 @@ func (r AIServicesHub) Read() sdk.ResourceFunc { hub.HighBusinessImpactEnabled = pointer.From(props.HbiWorkspace) hub.ImageBuildComputeName = pointer.From(props.ImageBuildCompute) hub.PublicNetworkAccess = string(*props.PublicNetworkAccess) - hub.DiscoveryUrl = pointer.From(props.DiscoveryUrl) + hub.DiscoveryUrl = pointer.From(props.DiscoveryURL) hub.WorkspaceId = pointer.From(props.WorkspaceId) hub.ManagedNetwork = flattenManagedNetwork(props.ManagedNetwork) @@ -535,7 +535,7 @@ func (r AIServicesHub) Read() sdk.ResourceFunc { } } -func (r AIServicesHub) Delete() sdk.ResourceFunc { +func (r AIFoundry) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { diff --git a/internal/services/aiservices/ai_services_hub_resource_test.go b/internal/services/machinelearning/ai_foundry_resource_test.go similarity index 89% rename from internal/services/aiservices/ai_services_hub_resource_test.go rename to internal/services/machinelearning/ai_foundry_resource_test.go index 63e3066d6934..32d3d5998ab6 100644 --- a/internal/services/aiservices/ai_services_hub_resource_test.go +++ b/internal/services/machinelearning/ai_foundry_resource_test.go @@ -1,4 +1,4 @@ -package aiservices_test +package machinelearning_test import ( "context" @@ -15,8 +15,8 @@ import ( type AIServicesHub struct{} -func TestAccAIServicesHub_basic(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") +func TestAccAIFoundry_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") r := AIServicesHub{} data.ResourceTest(t, r, []acceptance.TestStep{ @@ -30,8 +30,8 @@ func TestAccAIServicesHub_basic(t *testing.T) { }) } -func TestAccAIServicesHub_requiresImport(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") +func TestAccAIFoundry_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") r := AIServicesHub{} data.ResourceTest(t, r, []acceptance.TestStep{ @@ -45,8 +45,8 @@ func TestAccAIServicesHub_requiresImport(t *testing.T) { }) } -func TestAccAIServicesHub_complete(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") +func TestAccAIFoundry_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") r := AIServicesHub{} data.ResourceTest(t, r, []acceptance.TestStep{ @@ -60,8 +60,8 @@ func TestAccAIServicesHub_complete(t *testing.T) { }) } -func TestAccAIServicesHub_update(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") +func TestAccAIFoundry_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") r := AIServicesHub{} data.ResourceTest(t, r, []acceptance.TestStep{ @@ -82,8 +82,8 @@ func TestAccAIServicesHub_update(t *testing.T) { }) } -func TestAccAIServicesHub_encryptionWithSystemAssignedId(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") +func TestAccAIFoundry_encryptionWithSystemAssignedId(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") r := AIServicesHub{} data.ResourceTest(t, r, []acceptance.TestStep{ @@ -97,8 +97,8 @@ func TestAccAIServicesHub_encryptionWithSystemAssignedId(t *testing.T) { }) } -func TestAccAIServicesHub_encryptionWithUserAssignedId(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_ai_services_hub", "test") +func TestAccAIFoundry_encryptionWithUserAssignedId(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") r := AIServicesHub{} data.ResourceTest(t, r, []acceptance.TestStep{ @@ -139,7 +139,7 @@ provider "azurerm" { %s -resource "azurerm_ai_services_hub" "test" { +resource "azurerm_ai_foundry" "test" { name = "acctestaihub-%[2]d" location = azurerm_ai_services.test.location resource_group_name = azurerm_resource_group.test.name @@ -187,7 +187,7 @@ resource "azurerm_user_assigned_identity" "test" { location = azurerm_resource_group.test.location } -resource "azurerm_ai_services_hub" "test" { +resource "azurerm_ai_foundry" "test" { name = "acctestaihub-%[2]d" location = azurerm_ai_services.test.location resource_group_name = azurerm_resource_group.test.name @@ -260,7 +260,7 @@ resource "azurerm_user_assigned_identity" "test2" { location = azurerm_resource_group.test.location } -resource "azurerm_ai_services_hub" "test" { +resource "azurerm_ai_foundry" "test" { name = "acctestaihub-%[2]d" location = azurerm_ai_services.test.location resource_group_name = azurerm_resource_group.test.name @@ -349,7 +349,7 @@ resource "azurerm_role_assignment" "test_kv" { principal_id = azurerm_user_assigned_identity.test.principal_id } -resource "azurerm_ai_services_hub" "test" { +resource "azurerm_ai_foundry" "test" { name = "acctestaihub-%[2]d" location = azurerm_ai_services.test.location resource_group_name = azurerm_resource_group.test.name @@ -408,7 +408,7 @@ resource "azurerm_key_vault_key" "test" { depends_on = [azurerm_key_vault.test, azurerm_key_vault_access_policy.test] } -resource "azurerm_ai_services_hub" "test" { +resource "azurerm_ai_foundry" "test" { name = "acctestaihub-%[2]d" location = azurerm_ai_services.test.location resource_group_name = azurerm_resource_group.test.name @@ -432,12 +432,12 @@ func (AIServicesHub) requiresImport(data acceptance.TestData) string { return fmt.Sprintf(` %s -resource "azurerm_ai_services_hub" "import" { - name = azurerm_ai_services_hub.test.name - location = azurerm_ai_services_hub.test.location - resource_group_name = azurerm_ai_services_hub.test.resource_group_name - storage_account_id = azurerm_ai_services_hub.test.storage_account_id - key_vault_id = azurerm_ai_services_hub.test.key_vault_id +resource "azurerm_ai_foundry" "import" { + name = azurerm_ai_foundry.test.name + location = azurerm_ai_foundry.test.location + resource_group_name = azurerm_ai_foundry.test.resource_group_name + storage_account_id = azurerm_ai_foundry.test.storage_account_id + key_vault_id = azurerm_ai_foundry.test.key_vault_id identity { type = "SystemAssigned" diff --git a/internal/services/machinelearning/registration.go b/internal/services/machinelearning/registration.go index 3b2b51fcae3c..065b560361e6 100644 --- a/internal/services/machinelearning/registration.go +++ b/internal/services/machinelearning/registration.go @@ -56,6 +56,8 @@ func (r Registration) DataSources() []sdk.DataSource { // Resources returns the typed Resources supported by this service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ + AIFoundry{}, + AIFoundryProject{}, MachineLearningDataStoreBlobStorage{}, MachineLearningDataStoreDataLakeGen2{}, MachineLearningDataStoreFileShare{}, From 1e2ffb556b02c67074180bdadb358ed787aa5d40 Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 28 Nov 2024 11:44:03 +0100 Subject: [PATCH 11/14] make generate --- .github/labeler-issue-triage.yml | 7 ++----- .github/labeler-pull-request-triage.yml | 5 ----- .teamcity/components/generated/services.kt | 1 - .../cognitive/ai_services_resource_test.go | 16 ++++++++-------- website/allowed-subcategories | 1 - 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index 417612db6d33..78d10db98286 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -17,9 +17,6 @@ service/aadb2c: service/advisor: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_advisor_((.|\n)*)###' -service/ai-services: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_ai_services((.|\n)*)###' - service/analysis: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_analysis_services_server((.|\n)*)###' @@ -69,7 +66,7 @@ service/cdn: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_cdn_((.|\n)*)###' service/cognitive-services: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_cognitive_((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(ai_services|cognitive_)((.|\n)*)###' service/communication: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(communication_service|email_communication_service|gallery_application|orchestrated_virtual_machine_scale_set\W+|restore_point_collection|virtual_machine_gallery_application_assignment\W+|virtual_machine_implicit_data_disk_from_source\W+|virtual_machine_restore_point\W+|virtual_machine_restore_point_collection\W+|virtual_machine_run_command\W+)((.|\n)*)###' @@ -201,7 +198,7 @@ service/logic: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_logic_app_((.|\n)*)###' service/machine-learning: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_machine_learning_((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(ai_foundry|machine_learning_)((.|\n)*)###' service/maintenance: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(maintenance_|public_maintenance_configurations)((.|\n)*)###' diff --git a/.github/labeler-pull-request-triage.yml b/.github/labeler-pull-request-triage.yml index a44c954ced6e..154f12cb7ac8 100644 --- a/.github/labeler-pull-request-triage.yml +++ b/.github/labeler-pull-request-triage.yml @@ -27,11 +27,6 @@ service/advisor: - any-glob-to-any-file: - internal/services/advisor/**/* -service/ai-services: -- changed-files: - - any-glob-to-any-file: - - internal/services/aiservices/**/* - service/analysis: - changed-files: - any-glob-to-any-file: diff --git a/.teamcity/components/generated/services.kt b/.teamcity/components/generated/services.kt index dc84eb4f580c..e6d7678a75f5 100644 --- a/.teamcity/components/generated/services.kt +++ b/.teamcity/components/generated/services.kt @@ -4,7 +4,6 @@ // to re-generate this file, run 'make generate' in the root of the repository var services = mapOf( "aadb2c" to "AAD B2C", - "aiservices" to "AI Services", "apimanagement" to "API Management", "advisor" to "Advisor", "analysisservices" to "Analysis Services", diff --git a/internal/services/cognitive/ai_services_resource_test.go b/internal/services/cognitive/ai_services_resource_test.go index 86a6811266b5..4aab5587037d 100644 --- a/internal/services/cognitive/ai_services_resource_test.go +++ b/internal/services/cognitive/ai_services_resource_test.go @@ -19,7 +19,7 @@ import ( type AIServices struct{} -func TestAccAIServices_basic(t *testing.T) { +func TestAccCognitiveAIServices_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -37,7 +37,7 @@ func TestAccAIServices_basic(t *testing.T) { }) } -func TestAccAIServices_requiresImport(t *testing.T) { +func TestAccCognitiveAIServices_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -52,7 +52,7 @@ func TestAccAIServices_requiresImport(t *testing.T) { }) } -func TestAccAIServices_complete(t *testing.T) { +func TestAccCognitiveAIServices_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -69,7 +69,7 @@ func TestAccAIServices_complete(t *testing.T) { }) } -func TestAccAIServices_update(t *testing.T) { +func TestAccCognitiveAIServices_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -94,7 +94,7 @@ func TestAccAIServices_update(t *testing.T) { }) } -func TestAccAIServices_networkACLs(t *testing.T) { +func TestAccCognitiveAIServices_networkACLs(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -116,7 +116,7 @@ func TestAccAIServices_networkACLs(t *testing.T) { }) } -func TestAccAIServices_identity(t *testing.T) { +func TestAccCognitiveAIServices_identity(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -156,7 +156,7 @@ func TestAccAIServices_identity(t *testing.T) { }) } -func TestAccAIServices_customerManagedKey_update(t *testing.T) { +func TestAccCognitiveAIServices_customerManagedKey_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_services", "test") r := AIServices{} @@ -189,7 +189,7 @@ func TestAccAIServices_customerManagedKey_update(t *testing.T) { }) } -func TestAccAIServices_KVHsmManagedKey(t *testing.T) { +func TestAccCognitiveAIServices_KVHsmManagedKey(t *testing.T) { if os.Getenv("ARM_TEST_HSM_KEY") == "" { t.Skip("Skipping as ARM_TEST_HSM_KEY is not specified") return diff --git a/website/allowed-subcategories b/website/allowed-subcategories index f650ae0f36a5..01e0bbb4265f 100644 --- a/website/allowed-subcategories +++ b/website/allowed-subcategories @@ -1,6 +1,5 @@ AAD B2C -AI Services API Management Active Directory Domain Services Advisor From 399b9e495da0bd55a692ac0d06fab15cc0bbeed5 Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 28 Nov 2024 11:49:32 +0100 Subject: [PATCH 12/14] update documentation --- ...html.markdown => ai_foundry.html.markdown} | 66 +++++++++---------- ...kdown => ai_foundry_project.html.markdown} | 50 +++++++------- website/docs/r/ai_services.html.markdown | 2 +- 3 files changed, 59 insertions(+), 59 deletions(-) rename website/docs/r/{ai_services_hub.html.markdown => ai_foundry.html.markdown} (66%) rename website/docs/r/{ai_services_project.html.markdown => ai_foundry_project.html.markdown} (69%) diff --git a/website/docs/r/ai_services_hub.html.markdown b/website/docs/r/ai_foundry.html.markdown similarity index 66% rename from website/docs/r/ai_services_hub.html.markdown rename to website/docs/r/ai_foundry.html.markdown index 96619321f23d..c640e9c4c58f 100644 --- a/website/docs/r/ai_services_hub.html.markdown +++ b/website/docs/r/ai_foundry.html.markdown @@ -1,14 +1,14 @@ --- -subcategory: "AI Services" +subcategory: "Machine Learning" layout: "azurerm" -page_title: "Azure Resource Manager: azurerm_ai_services_hub" +page_title: "Azure Resource Manager: azurerm_ai_foundry" description: |- - Manages an AI Services Hub. + Manages an AI Foundry Hub. --- -# azurerm_ai_services_hub +# azurerm_ai_foundry -Manages an AI Services Hub. +Manages an AI Foundry Hub. ## Example Usage @@ -59,7 +59,7 @@ resource "azurerm_ai_services" "example" { sku_name = "S0" } -resource "azurerm_ai_services_hub" "example" { +resource "azurerm_ai_foundry" "example" { name = "exampleaihub" location = azurerm_ai_services.example.location resource_group_name = azurerm_resource_group.example.name @@ -76,43 +76,43 @@ resource "azurerm_ai_services_hub" "example" { The following arguments are supported: -* `name` - (Required) The name which should be used for this AI Services Hub. Changing this forces a new AI Services Hub to be created. +* `name` - (Required) The name which should be used for this AI Foundry Hub. Changing this forces a new AI Foundry Hub to be created. -* `location` - (Required) The Azure Region where the AI Services Hub should exist. Changing this forces a new AI Services Hub to be created. +* `location` - (Required) The Azure Region where the AI Foundry Hub should exist. Changing this forces a new AI Foundry Hub to be created. -* `resource_group_name` - (Required) The name of the Resource Group where the AI Services Hub should exist. Changing this forces a new AI Services Hub to be created. +* `resource_group_name` - (Required) The name of the Resource Group where the AI Foundry Hub should exist. Changing this forces a new AI Foundry Hub to be created. * `identity` - (Required) A `identity` block as defined below. -* `key_vault_id` - (Required) The Key Vault ID that should be used by this AI Services Hub. Changing this forces a new AI Services Hub to be created. +* `key_vault_id` - (Required) The Key Vault ID that should be used by this AI Foundry Hub. Changing this forces a new AI Foundry Hub to be created. -* `storage_account_id` - (Required) The Storage Account ID that should be used by this AI Services Hub. Changing this forces a new AI Services Hub to be created. +* `storage_account_id` - (Required) The Storage Account ID that should be used by this AI Foundry Hub. Changing this forces a new AI Foundry Hub to be created. --- -* `application_insights_id` - (Optional) The Application Insights ID that should be used by this AI Services Hub. +* `application_insights_id` - (Optional) The Application Insights ID that should be used by this AI Foundry Hub. -* `container_registry_id` - (Optional) The Container Registry ID that should be used by this AI Services Hub. +* `container_registry_id` - (Optional) The Container Registry ID that should be used by this AI Foundry Hub. -* `description` - (Optional) The description of this AI Services Hub. +* `description` - (Optional) The description of this AI Foundry Hub. -* `encryption` - (Optional) An `encryption` block as defined below. Changing this forces a new AI Services Hub to be created. +* `encryption` - (Optional) An `encryption` block as defined below. Changing this forces a new AI Foundry Hub to be created. -* `friendly_name` - (Optional) The display name of this AI Services Hub. +* `friendly_name` - (Optional) The display name of this AI Foundry Hub. -* `high_business_impact_enabled` - (Optional) Whether High Business Impact (HBI) should be enabled or not. Enabling this setting will reduce diagnostic data collected by the service. Changing this forces a new AI Services Hub to be created. Defaults to `false`. +* `high_business_impact_enabled` - (Optional) Whether High Business Impact (HBI) should be enabled or not. Enabling this setting will reduce diagnostic data collected by the service. Changing this forces a new AI Foundry Hub to be created. Defaults to `false`. --> **Note:** `high_business_impact_enabled` will be enabled by default when creating an AI Services Hub with `encryption` enabled. +-> **Note:** `high_business_impact_enabled` will be enabled by default when creating an AI Foundry Hub with `encryption` enabled. -* `image_build_compute_name` - (Optional) The compute name for image build of the AI Services Hub. +* `image_build_compute_name` - (Optional) The compute name for image build of the AI Foundry Hub. * `managed_network` - (Optional) A `managed_network` block as defined below. -* `primary_user_assigned_identity` - (Optional) The user assigned identity ID that represents the AI Services Hub identity. This must be set when enabling encryption with a user assigned identity. +* `primary_user_assigned_identity` - (Optional) The user assigned identity ID that represents the AI Foundry Hub identity. This must be set when enabling encryption with a user assigned identity. * `public_network_access` - (Optional) Whether public network access for this AI Service Hub should be enabled. Possible values include `Enabled` and `Disabled`. Defaults to `Enabled`. -* `tags` - (Optional) A mapping of tags which should be assigned to the AI Services Hub. +* `tags` - (Optional) A mapping of tags which should be assigned to the AI Foundry Hub. --- @@ -130,9 +130,9 @@ A `encryption` block supports the following: A `identity` block supports the following: -* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this AI Services Hub. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). +* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this AI Foundry Hub. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). -* `identity_ids` - (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this AI Services Hub. +* `identity_ids` - (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this AI Foundry Hub. ~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. @@ -140,17 +140,17 @@ A `identity` block supports the following: A `managed_network` block supports the following: -* `isolation_mode` - (Optional) The isolation mode of the AI Services Hub. Possible values are `Disabled`, `AllowOnlyApprovedOutbound`, and `AllowInternetOutbound`. +* `isolation_mode` - (Optional) The isolation mode of the AI Foundry Hub. Possible values are `Disabled`, `AllowOnlyApprovedOutbound`, and `AllowInternetOutbound`. ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: -* `id` - The ID of the AI Services Hub. +* `id` - The ID of the AI Foundry Hub. -* `discovery_url` - The URL for the discovery service to identify regional endpoints for AI Services Hub services. +* `discovery_url` - The URL for the discovery service to identify regional endpoints for AI Foundry Hub services. -* `workspace_id` - The immutable ID associated with this AI Services Hub. +* `workspace_id` - The immutable ID associated with this AI Foundry Hub. --- @@ -164,15 +164,15 @@ An `identity` block exports the following: The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: -* `create` - (Defaults to 1 hour) Used when creating the AI Services Hub. -* `read` - (Defaults to 5 minutes) Used when retrieving the AI Services Hub. -* `update` - (Defaults to 30 minutes) Used when updating the AI Services Hub. -* `delete` - (Defaults to 30 minutes) Used when deleting the AI Services Hub. +* `create` - (Defaults to 1 hour) Used when creating the AI Foundry Hub. +* `read` - (Defaults to 5 minutes) Used when retrieving the AI Foundry Hub. +* `update` - (Defaults to 30 minutes) Used when updating the AI Foundry Hub. +* `delete` - (Defaults to 30 minutes) Used when deleting the AI Foundry Hub. ## Import -AI Services Hubs can be imported using the `resource id`, e.g. +AI Foundry Hubs can be imported using the `resource id`, e.g. ```shell -terraform import azurerm_ai_services_hub.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.MachineLearningServices/workspaces/hub1 +terraform import azurerm_ai_foundry.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.MachineLearningServices/workspaces/hub1 ``` diff --git a/website/docs/r/ai_services_project.html.markdown b/website/docs/r/ai_foundry_project.html.markdown similarity index 69% rename from website/docs/r/ai_services_project.html.markdown rename to website/docs/r/ai_foundry_project.html.markdown index 5bff3f0aa796..d840e561000a 100644 --- a/website/docs/r/ai_services_project.html.markdown +++ b/website/docs/r/ai_foundry_project.html.markdown @@ -1,14 +1,14 @@ --- -subcategory: "AI Services" +subcategory: "Machine Learning" layout: "azurerm" -page_title: "Azure Resource Manager: azurerm_ai_services_project" +page_title: "Azure Resource Manager: azurerm_ai_foundry_project" description: |- - Manages an AI Services Project. + Manages an AI Foundry Project. --- -# azurerm_ai_services_project +# azurerm_ai_foundry_project -Manages an AI Services Project. +Manages an AI Foundry Project. ## Example Usage @@ -59,7 +59,7 @@ resource "azurerm_ai_services" "example" { sku_name = "S0" } -resource "azurerm_ai_services_hub" "example" { +resource "azurerm_ai_foundry" "example" { name = "exampleaihub" location = azurerm_ai_services.example.location resource_group_name = azurerm_resource_group.example.name @@ -71,7 +71,7 @@ resource "azurerm_ai_services_hub" "example" { } } -resource "azurerm_ai_services_project" "example" { +resource "azurerm_ai_foundry_project" "example" { name = "example" location = azurerm_ai_services_hub.example.location ai_services_hub_id = azurerm_ai_services_hub.example.id @@ -82,33 +82,33 @@ resource "azurerm_ai_services_project" "example" { The following arguments are supported: -* `name` - (Required) The name which should be used for this AI Services Project. Changing this forces a new AI Services Project to be created. +* `name` - (Required) The name which should be used for this AI Foundry Project. Changing this forces a new AI Foundry Project to be created. -* `location` - (Required) The Azure Region where the AI Services Project should exist. Changing this forces a new AI Services Project to be created. +* `location` - (Required) The Azure Region where the AI Foundry Project should exist. Changing this forces a new AI Foundry Project to be created. -* `ai_services_hub_id` - (Required) The AI Services Hub ID under which this Project should be created. Changing this forces a new AI Services Project to be created. +* `ai_services_hub_id` - (Required) The AI Services Hub ID under which this Project should be created. Changing this forces a new AI Foundry Project to be created. --- -* `description` - (Optional) The description of this AI Services Project. +* `description` - (Optional) The description of this AI Foundry Project. -* `friendly_name` - (Optional) The display name of this AI Services Project. +* `friendly_name` - (Optional) The display name of this AI Foundry Project. -* `high_business_impact_enabled` - (Optional) Whether High Business Impact (HBI) should be enabled or not. Enabling this setting will reduce diagnostic data collected by the service. Changing this forces a new AI Services Project to be created. Defaults to `false`. +* `high_business_impact_enabled` - (Optional) Whether High Business Impact (HBI) should be enabled or not. Enabling this setting will reduce diagnostic data collected by the service. Changing this forces a new AI Foundry Project to be created. Defaults to `false`. * `identity` - (Optional) A `identity` block as defined below. -* `image_build_compute_name` - (Optional) The compute name for image build of the AI Services Project. +* `image_build_compute_name` - (Optional) The compute name for image build of the AI Foundry Project. -* `tags` - (Optional) A mapping of tags which should be assigned to the AI Services Project. +* `tags` - (Optional) A mapping of tags which should be assigned to the AI Foundry Project. --- A `identity` block supports the following: -* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this AI Services Project. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). +* `type` - (Required) Specifies the type of Managed Service Identity that should be configured on this AI Foundry Project. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). -* `identity_ids` - (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this AI Services Project. +* `identity_ids` - (Optional) Specifies a list of User Assigned Managed Identity IDs to be assigned to this AI Foundry Project. ~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. @@ -116,9 +116,9 @@ A `identity` block supports the following: In addition to the Arguments listed above - the following Attributes are exported: -* `id` - The ID of the AI Services Project. +* `id` - The ID of the AI Foundry Project. -* `project_id` - The immutable project ID associated with this AI Services Project. +* `project_id` - The immutable project ID associated with this AI Foundry Project. --- @@ -132,15 +132,15 @@ An `identity` block exports the following: The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: -* `create` - (Defaults to 30 hour) Used when creating the AI Services Project. -* `read` - (Defaults to 5 minutes) Used when retrieving the AI Services Project. -* `update` - (Defaults to 30 minutes) Used when updating the AI Services Project. -* `delete` - (Defaults to 30 minutes) Used when deleting the AI Services Project. +* `create` - (Defaults to 30 hour) Used when creating the AI Foundry Project. +* `read` - (Defaults to 5 minutes) Used when retrieving the AI Foundry Project. +* `update` - (Defaults to 30 minutes) Used when updating the AI Foundry Project. +* `delete` - (Defaults to 30 minutes) Used when deleting the AI Foundry Project. ## Import -AI Services Projects can be imported using the `resource id`, e.g. +AI Foundry Projects can be imported using the `resource id`, e.g. ```shell -terraform import azurerm_ai_services_project.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.MachineLearningServices/workspaces/project1 +terraform import azurerm_ai_foundry_project.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.MachineLearningServices/workspaces/project1 ``` diff --git a/website/docs/r/ai_services.html.markdown b/website/docs/r/ai_services.html.markdown index e490d09277a6..9e5ac2eda0fb 100644 --- a/website/docs/r/ai_services.html.markdown +++ b/website/docs/r/ai_services.html.markdown @@ -1,5 +1,5 @@ --- -subcategory: "AI Services" +subcategory: "Cognitive Services" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_ai_services" description: |- From afa745d02cfe8b4e38578301193f8bb2ff621e4e Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 28 Nov 2024 14:18:34 +0100 Subject: [PATCH 13/14] fix failing update test for ai services and update requires import error check with correct resource name --- .../cognitive/ai_services_resource_test.go | 131 +++++++++++++++++- .../ai_foundry_project_resource.go | 2 +- .../ai_foundry_project_resource_test.go | 6 +- .../machinelearning/ai_foundry_resource.go | 2 +- .../ai_foundry_resource_test.go | 32 ++--- 5 files changed, 145 insertions(+), 28 deletions(-) diff --git a/internal/services/cognitive/ai_services_resource_test.go b/internal/services/cognitive/ai_services_resource_test.go index 4aab5587037d..7aaf2772b443 100644 --- a/internal/services/cognitive/ai_services_resource_test.go +++ b/internal/services/cognitive/ai_services_resource_test.go @@ -75,20 +75,15 @@ func TestAccCognitiveAIServices_update(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.basic(data), + Config: r.complete(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("tags.%").HasValue("0"), - check.That(data.ResourceName).Key("primary_access_key").Exists(), - check.That(data.ResourceName).Key("secondary_access_key").Exists(), ), }, { - Config: r.complete(data), + Config: r.update(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("tags.%").HasValue("1"), - check.That(data.ResourceName).Key("tags.Acceptance").HasValue("Test"), ), }, }) @@ -466,6 +461,128 @@ resource "azurerm_ai_services" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomIntOfLength(8)) } +func (AIServices) update(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cognitive-%[1]d" + location = "%[2]s" +} + +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + name = "%[3]s" +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv%[3]s" + 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" + soft_delete_retention_days = 7 + purge_protection_enabled = true + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + key_permissions = [ + "Get", "Create", "Delete", "List", "Restore", "Recover", "UnwrapKey", "WrapKey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify", "GetRotationPolicy" + ] + secret_permissions = [ + "Get", + ] + } + + access_policy { + tenant_id = azurerm_user_assigned_identity.test.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + key_permissions = [ + "Get", "Create", "Delete", "List", "Restore", "Recover", "UnwrapKey", "WrapKey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify", "GetRotationPolicy" + ] + secret_permissions = [ + "Get", + ] + } +} + +resource "azurerm_key_vault_key" "test" { + name = "acctestkvkey%[3]s" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + key_opts = ["decrypt", "encrypt", "sign", "unwrapKey", "verify", "wrapKey"] +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[1]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test_a" { + name = "acctestsubneta%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] + service_endpoints = ["Microsoft.CognitiveServices"] +} + +resource "azurerm_subnet" "test_b" { + name = "acctestsubnetb%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.4.0/24"] + service_endpoints = ["Microsoft.CognitiveServices"] +} + +resource "azurerm_ai_services" "test" { + name = "acctestcogacc-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "S0" + fqdns = ["foo.com"] + local_authentication_enabled = true + outbound_network_access_restricted = true + public_network_access = "Enabled" + custom_subdomain_name = "acctestcogacc-%[1]d" + + customer_managed_key { + key_vault_key_id = azurerm_key_vault_key.test.id + identity_client_id = azurerm_user_assigned_identity.test.client_id + } + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id + ] + } + network_acls { + default_action = "Allow" + virtual_network_rules { + subnet_id = azurerm_subnet.test_a.id + } + virtual_network_rules { + subnet_id = azurerm_subnet.test_b.id + } + } + + tags = { + Acceptance = "Test" + Environment = "Dev" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomIntOfLength(8)) +} + func (r AIServices) networkACLs(data acceptance.TestData) string { return fmt.Sprintf(` %s diff --git a/internal/services/machinelearning/ai_foundry_project_resource.go b/internal/services/machinelearning/ai_foundry_project_resource.go index f649fc86d123..d92a270e4c37 100644 --- a/internal/services/machinelearning/ai_foundry_project_resource.go +++ b/internal/services/machinelearning/ai_foundry_project_resource.go @@ -160,7 +160,7 @@ func (r AIFoundryProject) Create() sdk.ResourceFunc { } } if !response.WasNotFound(existing.HttpResponse) { - return tf.ImportAsExistsError("azurerm_ai_services_project", id.ID()) + return tf.ImportAsExistsError("azurerm_ai_foundry_project", id.ID()) } payload := workspaces.Workspace{ diff --git a/internal/services/machinelearning/ai_foundry_project_resource_test.go b/internal/services/machinelearning/ai_foundry_project_resource_test.go index b08a42ff3d29..f16d10576c8d 100644 --- a/internal/services/machinelearning/ai_foundry_project_resource_test.go +++ b/internal/services/machinelearning/ai_foundry_project_resource_test.go @@ -109,7 +109,7 @@ resource "azurerm_ai_foundry_project" "test" { type = "SystemAssigned" } } -`, AIServicesHub{}.basic(data), data.RandomInteger) +`, AIFoundry{}.basic(data), data.RandomInteger) } func (r AIFoundryProject) complete(data acceptance.TestData) string { @@ -134,7 +134,7 @@ resource "azurerm_ai_foundry_project" "test" { model = "regression" } } -`, AIServicesHub{}.complete(data), data.RandomInteger) +`, AIFoundry{}.complete(data), data.RandomInteger) } func (r AIFoundryProject) update(data acceptance.TestData) string { @@ -166,7 +166,7 @@ resource "azurerm_ai_foundry_project" "test" { env = "test" } } -`, AIServicesHub{}.complete(data), data.RandomInteger) +`, AIFoundry{}.complete(data), data.RandomInteger) } func (AIFoundryProject) requiresImport(data acceptance.TestData) string { diff --git a/internal/services/machinelearning/ai_foundry_resource.go b/internal/services/machinelearning/ai_foundry_resource.go index dcc24daebfbd..66edcaf6aa7d 100644 --- a/internal/services/machinelearning/ai_foundry_resource.go +++ b/internal/services/machinelearning/ai_foundry_resource.go @@ -257,7 +257,7 @@ func (r AIFoundry) Create() sdk.ResourceFunc { } } if !response.WasNotFound(existing.HttpResponse) { - return tf.ImportAsExistsError("azurerm_ai_services_hub", id.ID()) + return tf.ImportAsExistsError("azurerm_ai_foundry", id.ID()) } storageAccountId, err := commonids.ParseStorageAccountID(model.StorageAccountId) diff --git a/internal/services/machinelearning/ai_foundry_resource_test.go b/internal/services/machinelearning/ai_foundry_resource_test.go index 32d3d5998ab6..46213b6ed858 100644 --- a/internal/services/machinelearning/ai_foundry_resource_test.go +++ b/internal/services/machinelearning/ai_foundry_resource_test.go @@ -13,11 +13,11 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" ) -type AIServicesHub struct{} +type AIFoundry struct{} func TestAccAIFoundry_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") - r := AIServicesHub{} + r := AIFoundry{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -32,7 +32,7 @@ func TestAccAIFoundry_basic(t *testing.T) { func TestAccAIFoundry_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") - r := AIServicesHub{} + r := AIFoundry{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -47,7 +47,7 @@ func TestAccAIFoundry_requiresImport(t *testing.T) { func TestAccAIFoundry_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") - r := AIServicesHub{} + r := AIFoundry{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -62,7 +62,7 @@ func TestAccAIFoundry_complete(t *testing.T) { func TestAccAIFoundry_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") - r := AIServicesHub{} + r := AIFoundry{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -84,7 +84,7 @@ func TestAccAIFoundry_update(t *testing.T) { func TestAccAIFoundry_encryptionWithSystemAssignedId(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") - r := AIServicesHub{} + r := AIFoundry{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -99,7 +99,7 @@ func TestAccAIFoundry_encryptionWithSystemAssignedId(t *testing.T) { func TestAccAIFoundry_encryptionWithUserAssignedId(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_ai_foundry", "test") - r := AIServicesHub{} + r := AIFoundry{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -112,7 +112,7 @@ func TestAccAIFoundry_encryptionWithUserAssignedId(t *testing.T) { }) } -func (AIServicesHub) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { +func (AIFoundry) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := workspaces.ParseWorkspaceID(state.ID) if err != nil { return nil, err @@ -126,7 +126,7 @@ func (AIServicesHub) Exists(ctx context.Context, clients *clients.Client, state return pointer.To(resp.Model != nil), nil } -func (r AIServicesHub) basic(data acceptance.TestData) string { +func (r AIFoundry) basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -153,7 +153,7 @@ resource "azurerm_ai_foundry" "test" { `, r.template(data), data.RandomInteger) } -func (r AIServicesHub) complete(data acceptance.TestData) string { +func (r AIFoundry) complete(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -221,7 +221,7 @@ resource "azurerm_ai_foundry" "test" { `, r.template(data), data.RandomInteger) } -func (r AIServicesHub) update(data acceptance.TestData) string { +func (r AIFoundry) update(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -296,7 +296,7 @@ resource "azurerm_ai_foundry" "test" { `, r.template(data), data.RandomInteger) } -func (r AIServicesHub) encryptionWithUserAssignedId(data acceptance.TestData) string { +func (r AIFoundry) encryptionWithUserAssignedId(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -379,7 +379,7 @@ resource "azurerm_ai_foundry" "test" { `, r.template(data), data.RandomInteger) } -func (r AIServicesHub) encryptionWithSystemAssignedId(data acceptance.TestData) string { +func (r AIFoundry) encryptionWithSystemAssignedId(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { @@ -427,8 +427,8 @@ resource "azurerm_ai_foundry" "test" { `, r.template(data), data.RandomInteger) } -func (AIServicesHub) requiresImport(data acceptance.TestData) string { - template := AIServicesHub{}.basic(data) +func (AIFoundry) requiresImport(data acceptance.TestData) string { + template := AIFoundry{}.basic(data) return fmt.Sprintf(` %s @@ -446,7 +446,7 @@ resource "azurerm_ai_foundry" "import" { `, template) } -func (AIServicesHub) template(data acceptance.TestData) string { +func (AIFoundry) template(data acceptance.TestData) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} From ee4f9a4b0be25f1e51459137d80df7abaa7e6481 Mon Sep 17 00:00:00 2001 From: Steph Date: Tue, 10 Dec 2024 10:58:23 +0100 Subject: [PATCH 14/14] remove image_build_compute_name as this is ML specific and not valid for AI foundry --- .../ai_foundry_project_resource.go | 16 ---------------- .../ai_foundry_project_resource_test.go | 2 -- .../machinelearning/ai_foundry_resource.go | 16 ---------------- .../machinelearning/ai_foundry_resource_test.go | 2 -- website/docs/r/ai_foundry.html.markdown | 2 -- website/docs/r/ai_foundry_project.html.markdown | 2 -- 6 files changed, 40 deletions(-) diff --git a/internal/services/machinelearning/ai_foundry_project_resource.go b/internal/services/machinelearning/ai_foundry_project_resource.go index d92a270e4c37..7fd6d7a87038 100644 --- a/internal/services/machinelearning/ai_foundry_project_resource.go +++ b/internal/services/machinelearning/ai_foundry_project_resource.go @@ -28,7 +28,6 @@ type AIFoundryProjectModel struct { AIServicesHubId string `tfschema:"ai_services_hub_id"` Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` HighBusinessImpactEnabled bool `tfschema:"high_business_impact_enabled"` - ImageBuildComputeName string `tfschema:"image_build_compute_name"` Description string `tfschema:"description"` FriendlyName string `tfschema:"friendly_name"` ProjectId string `tfschema:"project_id"` @@ -103,12 +102,6 @@ func (r AIFoundryProject) Arguments() map[string]*pluginsdk.Schema { ForceNew: true, }, - "image_build_compute_name": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "description": { Type: pluginsdk.TypeString, Optional: true, @@ -193,10 +186,6 @@ func (r AIFoundryProject) Create() sdk.ResourceFunc { payload.Properties.HbiWorkspace = pointer.To(model.HighBusinessImpactEnabled) } - if model.ImageBuildComputeName != "" { - payload.Properties.ImageBuildCompute = pointer.To(model.ImageBuildComputeName) - } - if err = client.CreateOrUpdateThenPoll(ctx, id, payload); err != nil { return fmt.Errorf("creating %s: %+v", id, err) } @@ -246,10 +235,6 @@ func (r AIFoundryProject) Update() sdk.ResourceFunc { payload.Properties.ApplicationInsights = nil payload.Properties.Encryption = nil - if metadata.ResourceData.HasChange("image_build_compute_name") { - payload.Properties.ImageBuildCompute = pointer.To(state.ImageBuildComputeName) - } - if metadata.ResourceData.HasChange("description") { payload.Properties.Description = pointer.To(state.Description) } @@ -324,7 +309,6 @@ func (r AIFoundryProject) Read() sdk.ResourceFunc { hub.Description = pointer.From(props.Description) hub.FriendlyName = pointer.From(props.FriendlyName) hub.HighBusinessImpactEnabled = pointer.From(props.HbiWorkspace) - hub.ImageBuildComputeName = pointer.From(props.ImageBuildCompute) hub.ProjectId = pointer.From(props.WorkspaceId) } } diff --git a/internal/services/machinelearning/ai_foundry_project_resource_test.go b/internal/services/machinelearning/ai_foundry_project_resource_test.go index f16d10576c8d..d2d26933b75a 100644 --- a/internal/services/machinelearning/ai_foundry_project_resource_test.go +++ b/internal/services/machinelearning/ai_foundry_project_resource_test.go @@ -121,7 +121,6 @@ resource "azurerm_ai_foundry_project" "test" { location = azurerm_ai_foundry.test.location ai_services_hub_id = azurerm_ai_foundry.test.id - image_build_compute_name = "projectbuild" description = "AI Project created by Terraform" friendly_name = "AI Project" high_business_impact_enabled = false @@ -152,7 +151,6 @@ resource "azurerm_ai_foundry_project" "test" { location = azurerm_ai_foundry.test.location ai_services_hub_id = azurerm_ai_foundry.test.id - image_build_compute_name = "projectbuildupdate" description = "AI Project updated by Terraform" friendly_name = "AI Project for OS models" high_business_impact_enabled = false diff --git a/internal/services/machinelearning/ai_foundry_resource.go b/internal/services/machinelearning/ai_foundry_resource.go index 66edcaf6aa7d..0e6f22267431 100644 --- a/internal/services/machinelearning/ai_foundry_resource.go +++ b/internal/services/machinelearning/ai_foundry_resource.go @@ -42,7 +42,6 @@ type AIFoundryModel struct { Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` PrimaryUserAssignedIdentity string `tfschema:"primary_user_assigned_identity"` HighBusinessImpactEnabled bool `tfschema:"high_business_impact_enabled"` - ImageBuildComputeName string `tfschema:"image_build_compute_name"` Description string `tfschema:"description"` FriendlyName string `tfschema:"friendly_name"` DiscoveryUrl string `tfschema:"discovery_url"` @@ -200,12 +199,6 @@ func (r AIFoundry) Arguments() map[string]*pluginsdk.Schema { ValidateFunc: validation.StringInSlice(workspaces.PossibleValuesForPublicNetworkAccess(), false), }, - "image_build_compute_name": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "description": { Type: pluginsdk.TypeString, Optional: true, @@ -316,10 +309,6 @@ func (r AIFoundry) Create() sdk.ResourceFunc { payload.Properties.HbiWorkspace = pointer.To(model.HighBusinessImpactEnabled) } - if model.ImageBuildComputeName != "" { - payload.Properties.ImageBuildCompute = pointer.To(model.ImageBuildComputeName) - } - if model.PrimaryUserAssignedIdentity != "" { userAssignedId, err := commonids.ParseUserAssignedIdentityID(model.PrimaryUserAssignedIdentity) if err != nil { @@ -396,10 +385,6 @@ func (r AIFoundry) Update() sdk.ResourceFunc { payload.Properties.PublicNetworkAccess = pointer.To(workspaces.PublicNetworkAccess(state.PublicNetworkAccess)) } - if metadata.ResourceData.HasChange("image_build_compute_name") { - payload.Properties.ImageBuildCompute = pointer.To(state.ImageBuildComputeName) - } - if metadata.ResourceData.HasChange("description") { payload.Properties.Description = pointer.To(state.Description) } @@ -508,7 +493,6 @@ func (r AIFoundry) Read() sdk.ResourceFunc { hub.Description = pointer.From(props.Description) hub.FriendlyName = pointer.From(props.FriendlyName) hub.HighBusinessImpactEnabled = pointer.From(props.HbiWorkspace) - hub.ImageBuildComputeName = pointer.From(props.ImageBuildCompute) hub.PublicNetworkAccess = string(*props.PublicNetworkAccess) hub.DiscoveryUrl = pointer.From(props.DiscoveryURL) hub.WorkspaceId = pointer.From(props.WorkspaceId) diff --git a/internal/services/machinelearning/ai_foundry_resource_test.go b/internal/services/machinelearning/ai_foundry_resource_test.go index 46213b6ed858..cb2d92d932d6 100644 --- a/internal/services/machinelearning/ai_foundry_resource_test.go +++ b/internal/services/machinelearning/ai_foundry_resource_test.go @@ -205,7 +205,6 @@ resource "azurerm_ai_foundry" "test" { container_registry_id = azurerm_container_registry.test.id primary_user_assigned_identity = azurerm_user_assigned_identity.test.id public_network_access = "Disabled" - image_build_compute_name = "buildtest" description = "AI Hub created by Terraform" friendly_name = "AI Hub" high_business_impact_enabled = true @@ -279,7 +278,6 @@ resource "azurerm_ai_foundry" "test" { container_registry_id = azurerm_container_registry.test.id primary_user_assigned_identity = azurerm_user_assigned_identity.test2.id public_network_access = "Enabled" - image_build_compute_name = "buildtestupdated" description = "AI Hub for Projects" friendly_name = "AI Hub for OS models" high_business_impact_enabled = true diff --git a/website/docs/r/ai_foundry.html.markdown b/website/docs/r/ai_foundry.html.markdown index c640e9c4c58f..8f19569a4361 100644 --- a/website/docs/r/ai_foundry.html.markdown +++ b/website/docs/r/ai_foundry.html.markdown @@ -104,8 +104,6 @@ The following arguments are supported: -> **Note:** `high_business_impact_enabled` will be enabled by default when creating an AI Foundry Hub with `encryption` enabled. -* `image_build_compute_name` - (Optional) The compute name for image build of the AI Foundry Hub. - * `managed_network` - (Optional) A `managed_network` block as defined below. * `primary_user_assigned_identity` - (Optional) The user assigned identity ID that represents the AI Foundry Hub identity. This must be set when enabling encryption with a user assigned identity. diff --git a/website/docs/r/ai_foundry_project.html.markdown b/website/docs/r/ai_foundry_project.html.markdown index d840e561000a..d00543b58114 100644 --- a/website/docs/r/ai_foundry_project.html.markdown +++ b/website/docs/r/ai_foundry_project.html.markdown @@ -98,8 +98,6 @@ The following arguments are supported: * `identity` - (Optional) A `identity` block as defined below. -* `image_build_compute_name` - (Optional) The compute name for image build of the AI Foundry Project. - * `tags` - (Optional) A mapping of tags which should be assigned to the AI Foundry Project. ---