From bc227603db03e87249e940ad40909c55ea2ef448 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 1 Dec 2021 16:55:55 +0100 Subject: [PATCH 1/7] bot base resource --- .../bot/bot_service_azure_bot_resource.go | 61 ++++ .../bot_service_azure_bot_resource_test.go | 56 ++++ .../services/bot/bot_service_base_resource.go | 312 ++++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 internal/services/bot/bot_service_azure_bot_resource.go create mode 100644 internal/services/bot/bot_service_azure_bot_resource_test.go create mode 100644 internal/services/bot/bot_service_base_resource.go diff --git a/internal/services/bot/bot_service_azure_bot_resource.go b/internal/services/bot/bot_service_azure_bot_resource.go new file mode 100644 index 000000000000..a4ea837a6b87 --- /dev/null +++ b/internal/services/bot/bot_service_azure_bot_resource.go @@ -0,0 +1,61 @@ +package bot + +import ( + "github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/bot/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type WebAppBotServiceResource struct { + base botBaseResource +} + +var _ sdk.Resource = WebAppBotServiceResource{} + +var _ sdk.ResourceWithUpdate = WebAppBotServiceResource{} + +func (r WebAppBotServiceResource) Arguments() map[string]*pluginsdk.Schema { + schema := map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + } + return r.base.arguments(schema) +} + +func (r WebAppBotServiceResource) Attributes() map[string]*pluginsdk.Schema { + return r.base.attributes() +} + +func (r WebAppBotServiceResource) ModelObject() interface{} { + return nil +} + +func (r WebAppBotServiceResource) ResourceType() string{ + return "azurerm_bot_service_web_app" +} + +func (r WebAppBotServiceResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.BotServiceID +} + +func (r WebAppBotServiceResource) Create() sdk.ResourceFunc { + return r.base.createFunc(r.ResourceType(), string(botservice.KindAzurebot)) +} + +func (r WebAppBotServiceResource) Read() sdk.ResourceFunc { + return r.base.readFunc() +} + +func (r WebAppBotServiceResource) Delete() sdk.ResourceFunc { + return r.base.deleteFunc() +} + +func (r WebAppBotServiceResource) Update() sdk.ResourceFunc { + return r.base.updateFunc() +} diff --git a/internal/services/bot/bot_service_azure_bot_resource_test.go b/internal/services/bot/bot_service_azure_bot_resource_test.go new file mode 100644 index 000000000000..0204fcc8b943 --- /dev/null +++ b/internal/services/bot/bot_service_azure_bot_resource_test.go @@ -0,0 +1,56 @@ +package bot + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/bot/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type BotServiceWebAppResource struct{ +} + +func (t BotServiceWebAppResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.BotServiceID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Bot.BotClient.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %v", *id, err) + } + + return utils.Bool(resp.Properties != nil), nil +} + +func (BotWebAppResource) basicConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_client_config" "current" { +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_bot_web_app" "test" { + name = "acctestdf%d" + location = "global" + resource_group_name = azurerm_resource_group.test.name + sku = "F0" + microsoft_app_id = data.azurerm_client_config.current.client_id + + tags = { + environment = "production" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} \ No newline at end of file diff --git a/internal/services/bot/bot_service_base_resource.go b/internal/services/bot/bot_service_base_resource.go new file mode 100644 index 000000000000..7d127e8a98f7 --- /dev/null +++ b/internal/services/bot/bot_service_base_resource.go @@ -0,0 +1,312 @@ +package bot + +import ( + "context" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/location" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/bot/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type botBaseResource struct{} + +func (br botBaseResource) arguments(fields map[string]*pluginsdk.Schema) map[string]*pluginsdk.Schema { + output := map[string]*pluginsdk.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "sku": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(botservice.SkuNameF0), + string(botservice.SkuNameS1), + }, false), + }, + + "microsoft_app_id": { + Type: pluginsdk.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "endpoint": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "developer_app_insights_key": { + Type: pluginsdk.TypeString, + Optional: true, + //Computed: true, + ValidateFunc: validation.IsUUID, + }, + + "developer_app_insights_api_key": { + Type: pluginsdk.TypeString, + Optional: true, + //Computed: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "developer_app_insights_application_id": { + Type: pluginsdk.TypeString, + Optional: true, + //Computed: true, + ValidateFunc: validation.IsUUID, + }, + + "luis_app_ids": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + + "luis_key": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "tags": tags.Schema(), + } + + for k, v := range fields { + output[k] = v + } + + return output +} + +func (br botBaseResource) attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (br botBaseResource) createFunc(resourceName, botKind string) sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 *time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Bot.BotClient + subscriptionId := metadata.Client.Account.SubscriptionId + + id := parse.NewBotServiceID(subscriptionId, metadata.ResourceData.Get("resource_group_name").(string), metadata.ResourceData.Get("name").(string)) + + existing, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError(resourceName, id.ID()) + } + + displayName := metadata.ResourceData.Get("display_name").(string) + if displayName == "" { + displayName = id.Name + } + + props := botservice.Bot{ + Location: utils.String(metadata.ResourceData.Get("location").(string)), + Sku: &botservice.Sku{ + Name: botservice.SkuName(metadata.ResourceData.Get("sku").(string)), + }, + Kind: botservice.Kind(botKind), + Properties: &botservice.BotProperties{ + DisplayName: utils.String(displayName), + Endpoint: utils.String(metadata.ResourceData.Get("endpoint").(string)), + MsaAppID: utils.String(metadata.ResourceData.Get("microsoft_app_id").(string)), + DeveloperAppInsightKey: utils.String(metadata.ResourceData.Get("developer_app_insights_key").(string)), + DeveloperAppInsightsAPIKey: utils.String(metadata.ResourceData.Get("developer_app_insights_api_key").(string)), + DeveloperAppInsightsApplicationID: utils.String(metadata.ResourceData.Get("developer_app_insights_application_id").(string)), + LuisAppIds: utils.ExpandStringSlice(metadata.ResourceData.Get("luis_app_ids").([]interface{})), + LuisKey: utils.String(metadata.ResourceData.Get("luis_key").(string)), + }, + Tags: tags.Expand(metadata.ResourceData.Get("tags").(map[string]interface{})), + } + + if _, err := client.Create(ctx, id.ResourceGroup, id.Name, props); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (br botBaseResource) readFunc() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Bot.BotClient + + id, err := parse.BotServiceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + metadata.ResourceData.Set("name", id.Name) + metadata.ResourceData.Set("resource_group_name", id.ResourceGroup) + metadata.ResourceData.Set("location", location.NormalizeNilable(resp.Location)) + + sku := "" + if v := resp.Sku; v != nil { + sku = string(v.Name) + } + metadata.ResourceData.Set("sku", sku) + + metadata.ResourceData.Set("tags", tags.FlattenAndSet(metadata.ResourceData, resp.Tags)) + + if props := resp.Properties; props != nil { + msAppId := "" + if v := props.MsaAppID; v != nil { + msAppId = *v + } + metadata.ResourceData.Set("microsoft_app_id", msAppId) + + displayName := "" + if v := props.DisplayName; v != nil { + displayName = *v + } + metadata.ResourceData.Set("display_name", displayName) + + endpoint := "" + if v := props.Endpoint; v != nil { + endpoint = *v + } + metadata.ResourceData.Set("endpoint", endpoint) + + key := "" + if v := props.DeveloperAppInsightKey; v != nil { + key = *v + } + metadata.ResourceData.Set("developer_app_insights_key", key) + + apiKey := "" + if v := props.DeveloperAppInsightsAPIKey; v != nil { + apiKey = *v + } + metadata.ResourceData.Set("developer_app_insights_api_key", apiKey) + + appInsightsId := "" + if v := props.DeveloperAppInsightsApplicationID; v != nil { + appInsightsId = *v + } + metadata.ResourceData.Set("developer_app_insights_application_id", appInsightsId) + + var luisAppIds []string + if v := props.LuisAppIds; v != nil { + luisAppIds = *v + } + metadata.ResourceData.Set("luis_app_ids", utils.FlattenStringSlice(&luisAppIds)) + } + + return nil + }, + } +} + +func (br botBaseResource) deleteFunc() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Bot.BotClient + id, err := parse.BotServiceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err = client.Delete(ctx, id.ResourceGroup, id.Name); err != nil { + fmt.Errorf("deleting %s: %+v", *id, err) + } + + return nil + }, + } +} + +func (br botBaseResource) updateFunc() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Bot.BotClient + id, err := parse.BotServiceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + existing, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if metadata.ResourceData.HasChange("display_name") { + existing.Properties.DisplayName = utils.String(metadata.ResourceData.Get("display_name").(string)) + } + + if metadata.ResourceData.HasChange("endpoint") { + existing.Properties.Endpoint = utils.String(metadata.ResourceData.Get("endpoint").(string)) + } + + if metadata.ResourceData.HasChange("developer_app_insights_key") { + existing.Properties.DeveloperAppInsightKey = utils.String(metadata.ResourceData.Get("developer_app_insights_key").(string)) + } + + if metadata.ResourceData.HasChange("developer_app_insights_api_key") { + existing.Properties.DeveloperAppInsightsAPIKey = utils.String(metadata.ResourceData.Get("developer_app_insights_api_key").(string)) + } + + if metadata.ResourceData.HasChange("developer_app_insights_application_id") { + existing.Properties.DeveloperAppInsightsApplicationID = utils.String(metadata.ResourceData.Get("developer_app_insights_application_id").(string)) + } + + if metadata.ResourceData.HasChange("luis_app_ids") { + existing.Properties.LuisAppIds = utils.ExpandStringSlice(metadata.ResourceData.Get("luis_app_ids").([]interface{})) + } + + if metadata.ResourceData.HasChange("luis_key") { + existing.Properties.LuisKey = utils.String(metadata.ResourceData.Get("luis_key").(string)) + } + + if _, err := client.Update(ctx, id.ResourceGroup, id.Name, existing); err != nil { + return fmt.Errorf("updating %s: %+v", *id, err) + } + + return nil + }, + } +} \ No newline at end of file From 85917cde5b6c14b4a4218f14f7b14452c3256bd4 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 1 Dec 2021 16:56:50 +0100 Subject: [PATCH 2/7] azure bot kind with test --- .../bot/bot_service_azure_bot_resource.go | 26 ++-- .../bot_service_azure_bot_resource_test.go | 111 ++++++++++++++++-- 2 files changed, 113 insertions(+), 24 deletions(-) diff --git a/internal/services/bot/bot_service_azure_bot_resource.go b/internal/services/bot/bot_service_azure_bot_resource.go index a4ea837a6b87..c6f7a03a2dda 100644 --- a/internal/services/bot/bot_service_azure_bot_resource.go +++ b/internal/services/bot/bot_service_azure_bot_resource.go @@ -8,15 +8,15 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" ) -type WebAppBotServiceResource struct { +type AzureBotServiceResource struct { base botBaseResource } -var _ sdk.Resource = WebAppBotServiceResource{} +var _ sdk.Resource = AzureBotServiceResource{} -var _ sdk.ResourceWithUpdate = WebAppBotServiceResource{} +var _ sdk.ResourceWithUpdate = AzureBotServiceResource{} -func (r WebAppBotServiceResource) Arguments() map[string]*pluginsdk.Schema { +func (r AzureBotServiceResource) Arguments() map[string]*pluginsdk.Schema { schema := map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, @@ -28,34 +28,34 @@ func (r WebAppBotServiceResource) Arguments() map[string]*pluginsdk.Schema { return r.base.arguments(schema) } -func (r WebAppBotServiceResource) Attributes() map[string]*pluginsdk.Schema { +func (r AzureBotServiceResource) Attributes() map[string]*pluginsdk.Schema { return r.base.attributes() } -func (r WebAppBotServiceResource) ModelObject() interface{} { +func (r AzureBotServiceResource) ModelObject() interface{} { return nil } -func (r WebAppBotServiceResource) ResourceType() string{ - return "azurerm_bot_service_web_app" +func (r AzureBotServiceResource) ResourceType() string{ + return "azurerm_bot_service_azure_bot" } -func (r WebAppBotServiceResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { +func (r AzureBotServiceResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { return validate.BotServiceID } -func (r WebAppBotServiceResource) Create() sdk.ResourceFunc { +func (r AzureBotServiceResource) Create() sdk.ResourceFunc { return r.base.createFunc(r.ResourceType(), string(botservice.KindAzurebot)) } -func (r WebAppBotServiceResource) Read() sdk.ResourceFunc { +func (r AzureBotServiceResource) Read() sdk.ResourceFunc { return r.base.readFunc() } -func (r WebAppBotServiceResource) Delete() sdk.ResourceFunc { +func (r AzureBotServiceResource) Delete() sdk.ResourceFunc { return r.base.deleteFunc() } -func (r WebAppBotServiceResource) Update() sdk.ResourceFunc { +func (r AzureBotServiceResource) Update() sdk.ResourceFunc { return r.base.updateFunc() } diff --git a/internal/services/bot/bot_service_azure_bot_resource_test.go b/internal/services/bot/bot_service_azure_bot_resource_test.go index 0204fcc8b943..fec0169b5e3b 100644 --- a/internal/services/bot/bot_service_azure_bot_resource_test.go +++ b/internal/services/bot/bot_service_azure_bot_resource_test.go @@ -1,19 +1,63 @@ -package bot +package bot_test import ( "context" "fmt" + "testing" + "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/services/bot/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) -type BotServiceWebAppResource struct{ +type BotServiceAzureBotResource struct{ +} + +func TestAccBotServiceAzureBot_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_service_azure_bot", "test") + r := BotServiceAzureBotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("sku").HasValue("F0"), + check.That(data.ResourceName).Key("tags.%").HasValue("1"), + check.That(data.ResourceName).Key("tags.environment").HasValue("test"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccBotServiceAzureBot_completeUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_service_azure_bot", "test") + r := BotServiceAzureBotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.completeUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceType).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) } -func (t BotServiceWebAppResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + +func (t BotServiceAzureBotResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.BotServiceID(state.ID) if err != nil { return nil, err @@ -27,7 +71,7 @@ func (t BotServiceWebAppResource) Exists(ctx context.Context, clients *clients.C return utils.Bool(resp.Properties != nil), nil } -func (BotWebAppResource) basicConfig(data acceptance.TestData) string { +func (BotServiceAzureBotResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -37,20 +81,65 @@ data "azurerm_client_config" "current" { } resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" + name = "acctestRG-%[1]d" + location = "%[2]s" } -resource "azurerm_bot_web_app" "test" { - name = "acctestdf%d" - location = "global" +resource "azurerm_bot_service_azure_bot" "test" { + name = "acctestdf%[1]d" resource_group_name = azurerm_resource_group.test.name + location = "global" sku = "F0" microsoft_app_id = data.azurerm_client_config.current.client_id tags = { - environment = "production" + environment = "test" + } +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (BotServiceAzureBotResource) completeUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +data "azurerm_client_config" "current" { +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_application_insights" "test" { + name = "acctestappinsights-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_type = "web" +} + +resource "azurerm_application_insights_api_key" "test" { + name = "acctestappinsightsapikey-%[1]d" + application_insights_id = azurerm_application_insights.test.id + read_permissions = ["aggregate", "api", "draft", "extendqueries", "search"] +} + +resource "azurerm_bot_service_azure_bot" "test" { + name = "acctestdf%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = "global" + microsoft_app_id = data.azurerm_client_config.current.client_id + sku = "F0" + + endpoint = "https://example.com" + developer_app_insights_api_key = azurerm_application_insights_api_key.test.api_key + developer_app_insights_application_id = azurerm_application_insights.test.app_id + + tags = { + environment = "test" } } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary) } \ No newline at end of file From 299fabefbeb301e8e6d1a99f521b0d1282133187 Mon Sep 17 00:00:00 2001 From: Steph Date: Wed, 1 Dec 2021 16:57:07 +0100 Subject: [PATCH 3/7] register bot typed resource --- internal/provider/services.go | 1 + internal/services/bot/registration.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/internal/provider/services.go b/internal/provider/services.go index 682779085552..ddc88294c36e 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -110,6 +110,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { appconfiguration.Registration{}, appservice.Registration{}, batch.Registration{}, + bot.Registration{}, costmanagement.Registration{}, eventhub.Registration{}, loadbalancer.Registration{}, diff --git a/internal/services/bot/registration.go b/internal/services/bot/registration.go index aa32e8688435..40de3f5552b3 100644 --- a/internal/services/bot/registration.go +++ b/internal/services/bot/registration.go @@ -1,11 +1,25 @@ package bot 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{} +var _ sdk.UntypedServiceRegistration = Registration{} + +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{} +} + +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + AzureBotServiceResource{}, + } +} + // Name is the name of this Service func (r Registration) Name() string { return "Bot" From 1b1851fe71794a37e9fa22ebde37c870a710a187 Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 2 Dec 2021 08:38:16 +0100 Subject: [PATCH 4/7] fix check in test and rename test case --- .../services/bot/bot_service_azure_bot_resource_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/services/bot/bot_service_azure_bot_resource_test.go b/internal/services/bot/bot_service_azure_bot_resource_test.go index fec0169b5e3b..08c392d5436b 100644 --- a/internal/services/bot/bot_service_azure_bot_resource_test.go +++ b/internal/services/bot/bot_service_azure_bot_resource_test.go @@ -47,9 +47,9 @@ func TestAccBotServiceAzureBot_completeUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.completeUpdate(data), + Config: r.update(data), Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceType).ExistsInAzure(r), + check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), @@ -99,7 +99,7 @@ resource "azurerm_bot_service_azure_bot" "test" { `, data.RandomInteger, data.Locations.Primary) } -func (BotServiceAzureBotResource) completeUpdate(data acceptance.TestData) string { +func (BotServiceAzureBotResource) update(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} From b0cc22c1404f7a6220bf6c24944181abf3781c4d Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 2 Dec 2021 17:36:00 +0100 Subject: [PATCH 5/7] added custom importer --- .../bot/bot_service_azure_bot_resource.go | 8 +++-- .../services/bot/bot_service_base_resource.go | 31 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/internal/services/bot/bot_service_azure_bot_resource.go b/internal/services/bot/bot_service_azure_bot_resource.go index c6f7a03a2dda..29fd783bb10c 100644 --- a/internal/services/bot/bot_service_azure_bot_resource.go +++ b/internal/services/bot/bot_service_azure_bot_resource.go @@ -12,10 +12,10 @@ type AzureBotServiceResource struct { base botBaseResource } -var _ sdk.Resource = AzureBotServiceResource{} - var _ sdk.ResourceWithUpdate = AzureBotServiceResource{} +var _ sdk.ResourceWithCustomImporter = AzureBotServiceResource{} + func (r AzureBotServiceResource) Arguments() map[string]*pluginsdk.Schema { schema := map[string]*pluginsdk.Schema{ "name": { @@ -59,3 +59,7 @@ func (r AzureBotServiceResource) Delete() sdk.ResourceFunc { func (r AzureBotServiceResource) Update() sdk.ResourceFunc { return r.base.updateFunc() } + +func (r AzureBotServiceResource) CustomImporter() sdk.ResourceRunFunc { + return r.base.importerFunc(string(botservice.KindAzurebot)) +} \ No newline at end of file diff --git a/internal/services/bot/bot_service_base_resource.go b/internal/services/bot/bot_service_base_resource.go index 7d127e8a98f7..4db2016bcc32 100644 --- a/internal/services/bot/bot_service_base_resource.go +++ b/internal/services/bot/bot_service_base_resource.go @@ -58,22 +58,23 @@ func (br botBaseResource) arguments(fields map[string]*pluginsdk.Schema) map[str "developer_app_insights_key": { Type: pluginsdk.TypeString, Optional: true, - //Computed: true, ValidateFunc: validation.IsUUID, }, "developer_app_insights_api_key": { Type: pluginsdk.TypeString, Optional: true, - //Computed: true, Sensitive: true, ValidateFunc: validation.StringIsNotEmpty, + DiffSuppressFunc: func(k, old, new string, d *pluginsdk.ResourceData) bool { + // This field for the api key isn't returned at all from Azure + return (new == d.Get(k).(string)) && (old == "") + }, }, "developer_app_insights_application_id": { Type: pluginsdk.TypeString, Optional: true, - //Computed: true, ValidateFunc: validation.IsUUID, }, @@ -309,4 +310,26 @@ func (br botBaseResource) updateFunc() sdk.ResourceFunc { return nil }, } -} \ No newline at end of file +} + +func (br botBaseResource) importerFunc(expectKind string) sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Bot.BotClient + + id, err := parse.BotServiceID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if actualKind := string(resp.Kind); actualKind != expectKind { + return fmt.Errorf("bot has mismatched type, expected: %q, got %q", expectKind, actualKind) + } + + return nil + } +} From 62703a705de86e83007d5066c9e1ed79033dd908 Mon Sep 17 00:00:00 2001 From: Steph Date: Thu, 2 Dec 2021 18:08:50 +0100 Subject: [PATCH 6/7] add requires import test --- .../bot_service_azure_bot_resource_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/services/bot/bot_service_azure_bot_resource_test.go b/internal/services/bot/bot_service_azure_bot_resource_test.go index 08c392d5436b..1a6c50e2aafb 100644 --- a/internal/services/bot/bot_service_azure_bot_resource_test.go +++ b/internal/services/bot/bot_service_azure_bot_resource_test.go @@ -56,6 +56,23 @@ func TestAccBotServiceAzureBot_completeUpdate(t *testing.T) { }) } +func TestAccBotServiceAzureBot_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_service_azure_bot", "test") + r := BotServiceAzureBotResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.requiresImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_bot_service_azure_bot"), + }, + }) +} func (t BotServiceAzureBotResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.BotServiceID(state.ID) @@ -142,4 +159,19 @@ resource "azurerm_bot_service_azure_bot" "test" { } } `, data.RandomInteger, data.Locations.Primary) +} + +func (BotServiceAzureBotResource) requiresImport(data acceptance.TestData) string { + template := BotServiceAzureBotResource{}.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_bot_service_azure_bot" "import" { + name = azurerm_bot_service_azure_bot.test.name + resource_group_name = azurerm_bot_service_azure_bot.test.resource_group_name + location = azurerm_bot_service_azure_bot.test.location + sku = azurerm_bot_service_azure_bot.test.sku + microsoft_app_id = azurerm_bot_service_azure_bot.test.microsoft_app_id +} +`, template) } \ No newline at end of file From 6d59226096b4aff1f5c2824ef24ff44bcccd9e8a Mon Sep 17 00:00:00 2001 From: Steph Date: Fri, 3 Dec 2021 10:43:22 +0100 Subject: [PATCH 7/7] add docs and fmt --- .../bot/bot_service_azure_bot_resource.go | 4 +- .../bot_service_azure_bot_resource_test.go | 6 +- .../services/bot/bot_service_base_resource.go | 52 ++++----- .../r/bot_service_azure_bot.html.markdown | 102 ++++++++++++++++++ 4 files changed, 133 insertions(+), 31 deletions(-) create mode 100644 website/docs/r/bot_service_azure_bot.html.markdown diff --git a/internal/services/bot/bot_service_azure_bot_resource.go b/internal/services/bot/bot_service_azure_bot_resource.go index 29fd783bb10c..12eaceaaf546 100644 --- a/internal/services/bot/bot_service_azure_bot_resource.go +++ b/internal/services/bot/bot_service_azure_bot_resource.go @@ -36,7 +36,7 @@ func (r AzureBotServiceResource) ModelObject() interface{} { return nil } -func (r AzureBotServiceResource) ResourceType() string{ +func (r AzureBotServiceResource) ResourceType() string { return "azurerm_bot_service_azure_bot" } @@ -62,4 +62,4 @@ func (r AzureBotServiceResource) Update() sdk.ResourceFunc { func (r AzureBotServiceResource) CustomImporter() sdk.ResourceRunFunc { return r.base.importerFunc(string(botservice.KindAzurebot)) -} \ No newline at end of file +} diff --git a/internal/services/bot/bot_service_azure_bot_resource_test.go b/internal/services/bot/bot_service_azure_bot_resource_test.go index 1a6c50e2aafb..ec52557b7678 100644 --- a/internal/services/bot/bot_service_azure_bot_resource_test.go +++ b/internal/services/bot/bot_service_azure_bot_resource_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/utils" ) -type BotServiceAzureBotResource struct{ +type BotServiceAzureBotResource struct { } func TestAccBotServiceAzureBot_basic(t *testing.T) { @@ -50,7 +50,7 @@ func TestAccBotServiceAzureBot_completeUpdate(t *testing.T) { Config: r.update(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - ), + ), }, data.ImportStep(), }) @@ -174,4 +174,4 @@ resource "azurerm_bot_service_azure_bot" "import" { microsoft_app_id = azurerm_bot_service_azure_bot.test.microsoft_app_id } `, template) -} \ No newline at end of file +} diff --git a/internal/services/bot/bot_service_base_resource.go b/internal/services/bot/bot_service_base_resource.go index 4db2016bcc32..c153ea1fb767 100644 --- a/internal/services/bot/bot_service_base_resource.go +++ b/internal/services/bot/bot_service_base_resource.go @@ -26,7 +26,7 @@ func (br botBaseResource) arguments(fields map[string]*pluginsdk.Schema) map[str "location": azure.SchemaLocation(), "sku": { - Type: pluginsdk.TypeString, + Type: pluginsdk.TypeString, Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ @@ -36,22 +36,22 @@ func (br botBaseResource) arguments(fields map[string]*pluginsdk.Schema) map[str }, "microsoft_app_id": { - Type: pluginsdk.TypeString, - ForceNew: true, - Required: true, + Type: pluginsdk.TypeString, + ForceNew: true, + Required: true, ValidateFunc: validation.IsUUID, }, "display_name": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, ValidateFunc: validation.StringIsNotEmpty, }, "endpoint": { - Type: pluginsdk.TypeString, - Optional: true, + Type: pluginsdk.TypeString, + Optional: true, ValidateFunc: validation.StringIsNotEmpty, }, @@ -110,7 +110,7 @@ func (br botBaseResource) attributes() map[string]*pluginsdk.Schema { func (br botBaseResource) createFunc(resourceName, botKind string) sdk.ResourceFunc { return sdk.ResourceFunc{ - Timeout: 30 *time.Minute, + Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.Bot.BotClient subscriptionId := metadata.Client.Account.SubscriptionId @@ -139,7 +139,7 @@ func (br botBaseResource) createFunc(resourceName, botKind string) sdk.ResourceF }, Kind: botservice.Kind(botKind), Properties: &botservice.BotProperties{ - DisplayName: utils.String(displayName), + DisplayName: utils.String(displayName), Endpoint: utils.String(metadata.ResourceData.Get("endpoint").(string)), MsaAppID: utils.String(metadata.ResourceData.Get("microsoft_app_id").(string)), DeveloperAppInsightKey: utils.String(metadata.ResourceData.Get("developer_app_insights_key").(string)), @@ -233,7 +233,7 @@ func (br botBaseResource) readFunc() sdk.ResourceFunc { if v := props.LuisAppIds; v != nil { luisAppIds = *v } - metadata.ResourceData.Set("luis_app_ids", utils.FlattenStringSlice(&luisAppIds)) + metadata.ResourceData.Set("luis_app_ids", utils.FlattenStringSlice(&luisAppIds)) } return nil @@ -252,7 +252,7 @@ func (br botBaseResource) deleteFunc() sdk.ResourceFunc { } if _, err = client.Delete(ctx, id.ResourceGroup, id.Name); err != nil { - fmt.Errorf("deleting %s: %+v", *id, err) + return fmt.Errorf("deleting %s: %+v", *id, err) } return nil @@ -314,22 +314,22 @@ func (br botBaseResource) updateFunc() sdk.ResourceFunc { func (br botBaseResource) importerFunc(expectKind string) sdk.ResourceRunFunc { return func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Bot.BotClient + client := metadata.Client.Bot.BotClient - id, err := parse.BotServiceID(metadata.ResourceData.Id()) - if err != nil { - return err - } + id, err := parse.BotServiceID(metadata.ResourceData.Id()) + if err != nil { + return err + } - resp, err := client.Get(ctx, id.ResourceGroup, id.Name) - if err != nil { - return fmt.Errorf("retrieving %s: %+v", *id, err) - } + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } - if actualKind := string(resp.Kind); actualKind != expectKind { - return fmt.Errorf("bot has mismatched type, expected: %q, got %q", expectKind, actualKind) - } + if actualKind := string(resp.Kind); actualKind != expectKind { + return fmt.Errorf("bot has mismatched type, expected: %q, got %q", expectKind, actualKind) + } - return nil + return nil } } diff --git a/website/docs/r/bot_service_azure_bot.html.markdown b/website/docs/r/bot_service_azure_bot.html.markdown new file mode 100644 index 000000000000..c0418c747043 --- /dev/null +++ b/website/docs/r/bot_service_azure_bot.html.markdown @@ -0,0 +1,102 @@ +--- +subcategory: "Bot" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_bot_service_azure_bot" +description: |- + Manages an Azure Bot Service. +--- + +# azurerm_bot_service_azure_bot + +Manages an Azure Bot Service. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_application_insights" "example" { + name = "example-appinsights" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + application_type = "web" +} + +resource "azurerm_application_insights_api_key" "example" { + name = "example-appinsightsapikey" + application_insights_id = azurerm_application_insights.example.id + read_permissions = ["aggregate", "api", "draft", "extendqueries", "search"] +} + +resource "azurerm_bot_service_azure_bot" "example" { + name = "exampleazurebot" + resource_group_name = azurerm_resource_group.example.name + location = "global" + microsoft_app_id = data.azurerm_client_config.current.client_id + sku = "F0" + + endpoint = "https://example.com" + developer_app_insights_api_key = azurerm_application_insights_api_key.example.api_key + developer_app_insights_application_id = azurerm_application_insights.example.app_id + + tags = { + environment = "test" + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Azure Bot Service. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Azure Bot Service should exist. Changing this forces a new resource to be created. + +* `location` - (Required) The supported Azure location where the Azure Bot Service should exist. Changing this forces a new resource to be created. + +* `microsoft_app_id` - (Required) The Microsoft Application ID for the Azure Bot Service. Changing this forces a new resource to be created. + +* `sku` - (Required) The SKU of the Azure Bot Service. Accepted values are `F0` or `S1`. Changing this forces a new resource to be created. + +* `developer_app_insights_api_key` - (Optional) The Application Insights Api Key to associate with this Azure Bot Service. + +* `developer_app_insights_application_id` - (Optional) The resource ID of the Application Insights instance to associate with this Azure Bot Service. + +* `developer_app_insights_key` - (Optional) The Application Insight Key to associate with this Azure Bot Service. + +* `display_name` - (Optional) The name that the Azure Bot Service will be displayed as. This defaults to the value set for `name` if not specified. + +* `endpoint` - (Optional) The Azure Bot Service endpoint. + +* `luis_app_ids` - (Optional) A list of LUIS App IDs to associate with this Azure Bot Service. + +* `luis_key` - (Optional) The LUIS key to associate with this Azure Bot Service. + +* `tags` - (Optional) A mapping of tags which should be assigned to this Azure Bot Service. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Azure Bot Service. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Azure Bot Service. +* `read` - (Defaults to 5 minutes) Used when retrieving the Azure Bot Service. +* `update` - (Defaults to 30 minutes) Used when updating the Azure Bot Service. +* `delete` - (Defaults to 30 minutes) Used when deleting the Azure Bot Service. + +## Import + +Azure Bot Services can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_bot_service_azure_bot.example /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.BotService/botServices/botService1 +``` \ No newline at end of file