From 913b0331deac441084930f2336df3cf91c3be1e1 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Mon, 19 Jul 2021 14:14:11 +0800 Subject: [PATCH 1/7] New Resource: azurerm_bot_channel_web_chat --- .../internal/services/bot/bot_channel_test.go | 1 + .../bot/bot_channel_web_chat_resource.go | 225 ++++++++++++++++++ .../bot/bot_channel_web_chat_resource_test.go | 63 +++++ azurerm/internal/services/bot/registration.go | 1 + .../services/bot/validate/bot_name.go | 31 +++ .../services/bot/validate/bot_name_test.go | 45 ++++ 6 files changed, 366 insertions(+) create mode 100644 azurerm/internal/services/bot/bot_channel_web_chat_resource.go create mode 100644 azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go create mode 100644 azurerm/internal/services/bot/validate/bot_name.go create mode 100644 azurerm/internal/services/bot/validate/bot_name_test.go diff --git a/azurerm/internal/services/bot/bot_channel_test.go b/azurerm/internal/services/bot/bot_channel_test.go index a36a3e5e31b0..26bae6a7adee 100644 --- a/azurerm/internal/services/bot/bot_channel_test.go +++ b/azurerm/internal/services/bot/bot_channel_test.go @@ -27,6 +27,7 @@ func TestAccBotChannelsRegistration(t *testing.T) { "directlineBasic": testAccBotChannelDirectline_basic, "directlineComplete": testAccBotChannelDirectline_complete, "directlineUpdate": testAccBotChannelDirectline_update, + "webchatBasic": testAccBotChannelWebChat_basic, }, "web_app": { "basic": testAccBotWebApp_basic, diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go new file mode 100644 index 000000000000..5d7f9b960169 --- /dev/null +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go @@ -0,0 +1,225 @@ +package bot + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceBotChannelWebChat() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceBotChannelWebChatCreate, + Read: resourceBotChannelWebChatRead, + Delete: resourceBotChannelWebChatDelete, + Update: resourceBotChannelWebChatUpdate, + + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.BotChannelID(id) + return err + }), + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "bot_name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.BotName, + }, + + "sites": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "site_name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "enabled_preview": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func resourceBotChannelWebChatCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id := parse.NewBotChannelID(subscriptionId, d.Get("resource_group_name").(string), d.Get("bot_name").(string), string(botservice.ChannelNameWebChatChannel)) + + // As Bot WebChat Channel would be created by default while creating Bot Registrations Channel + // So it has to leverage the default one + + channel := botservice.BotChannel{ + Properties: botservice.WebChatChannel{ + Properties: &botservice.WebChatChannelProperties{ + Sites: expandWebChatSites(d.Get("sites").(*pluginsdk.Set).List()), + }, + ChannelName: botservice.ChannelNameBasicChannelChannelNameWebChatChannel, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Create(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + d.SetId(id.ID()) + return resourceBotChannelWebChatRead(d, meta) +} + +func resourceBotChannelWebChatRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.BotChannelID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.BotServiceName, string(botservice.ChannelNameWebChatChannel)) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] %s was not found - removing from state", id) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + d.Set("bot_name", id.BotServiceName) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("location", location.NormalizeNilable(resp.Location)) + + if props := resp.Properties; props != nil { + if channel, ok := props.AsWebChatChannel(); ok { + if channelProps := channel.Properties; channelProps != nil { + if err := d.Set("sites", flattenWebChatSites(channelProps.Sites)); err != nil { + return fmt.Errorf("setting `sites`: %+v", err) + } + } + } + } + + return nil +} + +func resourceBotChannelWebChatUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.BotChannelID(d.Id()) + if err != nil { + return err + } + + channel := botservice.BotChannel{ + Properties: botservice.WebChatChannel{ + Properties: &botservice.WebChatChannelProperties{ + Sites: expandWebChatSites(d.Get("sites").(*pluginsdk.Set).List()), + }, + ChannelName: botservice.ChannelNameBasicChannelChannelNameWebChatChannel, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Update(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return resourceBotChannelWebChatRead(d, meta) +} + +func resourceBotChannelWebChatDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.BotChannelID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Delete(ctx, id.ResourceGroup, id.BotServiceName, string(botservice.ChannelNameWebChatChannel)) + if err != nil { + if !response.WasNotFound(resp.Response) { + return fmt.Errorf("deleting %s: %+v", id, err) + } + } + + return nil +} + +func expandWebChatSites(input []interface{}) *[]botservice.WebChatSite { + results := make([]botservice.WebChatSite, 0) + for _, item := range input { + v := item.(map[string]interface{}) + + results = append(results, botservice.WebChatSite{ + EnablePreview: utils.Bool(v["enabled_preview"].(bool)), + SiteName: utils.String(v["site_name"].(string)), + IsEnabled: utils.Bool(true), + }) + } + return &results +} + +func flattenWebChatSites(input *[]botservice.WebChatSite) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + var enablePreview bool + if item.EnablePreview != nil { + enablePreview = *item.EnablePreview + } + + var siteName string + if item.SiteName != nil { + siteName = *item.SiteName + } + + results = append(results, map[string]interface{}{ + "enabled_preview": enablePreview, + "site_name": siteName, + }) + } + return results +} diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go new file mode 100644 index 000000000000..56fb331c135b --- /dev/null +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go @@ -0,0 +1,63 @@ +package bot_test + +import ( + "context" + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type BotChannelWebChatResource struct { +} + +func testAccBotChannelWebChat_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_web_chat", "test") + r := BotChannelWebChatResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (t BotChannelWebChatResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.BotChannelID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Bot.ChannelClient.Get(ctx, id.ResourceGroup, id.BotServiceName, string(botservice.ChannelNameWebChatChannel)) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %v", id.String(), err) + } + + return utils.Bool(resp.Properties != nil), nil +} + +func (BotChannelWebChatResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_web_chat" "test" { + bot_name = azurerm_bot_channels_registration.test.name + location = azurerm_bot_channels_registration.test.location + resource_group_name = azurerm_resource_group.test.name + + sites { + enabled_preview = true + } +} +`, BotChannelsRegistrationResource{}.basicConfig(data)) +} diff --git a/azurerm/internal/services/bot/registration.go b/azurerm/internal/services/bot/registration.go index 52b31080528d..ac308dc0bf54 100644 --- a/azurerm/internal/services/bot/registration.go +++ b/azurerm/internal/services/bot/registration.go @@ -30,6 +30,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_bot_channel_email": resourceBotChannelEmail(), "azurerm_bot_channel_ms_teams": resourceBotChannelMsTeams(), "azurerm_bot_channel_slack": resourceBotChannelSlack(), + "azurerm_bot_channel_web_chat": resourceBotChannelWebChat(), "azurerm_bot_channels_registration": resourceBotChannelsRegistration(), "azurerm_bot_connection": resourceArmBotConnection(), "azurerm_healthbot": resourceHealthbotService(), diff --git a/azurerm/internal/services/bot/validate/bot_name.go b/azurerm/internal/services/bot/validate/bot_name.go new file mode 100644 index 000000000000..36131fb7ffcd --- /dev/null +++ b/azurerm/internal/services/bot/validate/bot_name.go @@ -0,0 +1,31 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func BotName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if len(v) < 4 { + errors = append(errors, fmt.Errorf("length should be greater than %d", 4)) + return + } + + if len(v) > 42 { + errors = append(errors, fmt.Errorf("length should be less than %d", 42)) + return + } + + if !regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`).MatchString(v) { + errors = append(errors, fmt.Errorf("%q must start with a letter or digit and may only contain alphanumeric characters, underscores and dashes", k)) + return + } + + return +} diff --git a/azurerm/internal/services/bot/validate/bot_name_test.go b/azurerm/internal/services/bot/validate/bot_name_test.go new file mode 100644 index 000000000000..4af8c6674df1 --- /dev/null +++ b/azurerm/internal/services/bot/validate/bot_name_test.go @@ -0,0 +1,45 @@ +package validate + +import ( + "strings" + "testing" +) + +func TestBotName(t *testing.T) { + testCases := []struct { + Input string + Expected bool + }{ + { + Input: "Test123", + Expected: true, + }, + { + Input: "Test_123", + Expected: true, + }, + { + Input: "Test-123", + Expected: true, + }, + { + Input: strings.Repeat("s", 41), + Expected: true, + }, + { + Input: strings.Repeat("s", 42), + Expected: true, + }, + { + Input: strings.Repeat("s", 43), + Expected: false, + }, + } + for _, v := range testCases { + _, errors := BotName(v.Input, "bot_name") + result := len(errors) == 0 + if result != v.Expected { + t.Fatalf("Expected the result to be %t but got %t (and %d errors)", v.Expected, result, len(errors)) + } + } +} From bfe0be3ff522cc7bea7e8c2bc14fc5a2a282819b Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Mon, 19 Jul 2021 17:28:57 +0800 Subject: [PATCH 2/7] update code --- .../bot/bot_channel_web_chat_resource.go | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go index 5d7f9b960169..ed4426b3d647 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go @@ -6,7 +6,6 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice" - "github.com/hashicorp/go-azure-helpers/response" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" @@ -50,7 +49,7 @@ func resourceBotChannelWebChat() *pluginsdk.Resource { "sites": { Type: pluginsdk.TypeSet, - Optional: true, + Required: true, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "site_name": { @@ -91,7 +90,7 @@ func resourceBotChannelWebChatCreate(d *pluginsdk.ResourceData, meta interface{} Kind: botservice.KindBot, } - if _, err := client.Create(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { + if _, err := client.Update(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { return fmt.Errorf("creating %s: %+v", id, err) } @@ -175,11 +174,25 @@ func resourceBotChannelWebChatDelete(d *pluginsdk.ResourceData, meta interface{} return err } - resp, err := client.Delete(ctx, id.ResourceGroup, id.BotServiceName, string(botservice.ChannelNameWebChatChannel)) - if err != nil { - if !response.WasNotFound(resp.Response) { - return fmt.Errorf("deleting %s: %+v", id, err) - } + defaultSite := "Default Site" + channel := botservice.BotChannel{ + Properties: botservice.WebChatChannel{ + Properties: &botservice.WebChatChannelProperties{ + Sites: &[]botservice.WebChatSite{ + { + SiteName: utils.String(defaultSite), + IsEnabled: utils.Bool(true), + }, + }, + }, + ChannelName: botservice.ChannelNameBasicChannelChannelNameWebChatChannel, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Update(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) } return nil From 2499bcfb35c653e32bc8dae6f8e9ae0e13bd56f7 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Tue, 20 Jul 2021 11:48:14 +0800 Subject: [PATCH 3/7] update code --- .../bot/bot_channel_web_chat_resource.go | 45 +++++++-- .../bot/bot_channel_web_chat_resource_test.go | 96 ++++++++++++++++++- website/docs/r/bot_channel_web_chat.markdown | 83 ++++++++++++++++ 3 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 website/docs/r/bot_channel_web_chat.markdown diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go index ed4426b3d647..0ed56f431954 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go @@ -7,6 +7,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/parse" @@ -76,8 +77,29 @@ func resourceBotChannelWebChatCreate(d *pluginsdk.ResourceData, meta interface{} id := parse.NewBotChannelID(subscriptionId, d.Get("resource_group_name").(string), d.Get("bot_name").(string), string(botservice.ChannelNameWebChatChannel)) - // As Bot WebChat Channel would be created by default while creating Bot Registrations Channel - // So it has to leverage the default one + existing, err := client.Get(ctx, id.ResourceGroup, id.BotServiceName, id.ChannelName) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + // The Bot WebChat Channel would be created by default while creating Bot Registrations Channel. + // So if the channel includes `Default Site`, it means it's default channel and delete it. + // So if the channel includes other site, it means it's user custom channel and throws conflict error. + if props := existing.Properties; props != nil { + defaultChannel, ok := props.AsWebChatChannel() + if ok && defaultChannel.Properties != nil { + if includeDefaultWebSite(defaultChannel.Properties.Sites) { + if _, err := client.Delete(ctx, id.ResourceGroup, id.BotServiceName, string(botservice.ChannelNameBasicChannelChannelNameWebChatChannel)); err != nil { + return fmt.Errorf("deleting the default Web Chat Channel %s: %+v", id, err) + } + } else { + return tf.ImportAsExistsError("azurerm_bot_channel_web_chat", id.ID()) + } + } + } + } channel := botservice.BotChannel{ Properties: botservice.WebChatChannel{ @@ -90,7 +112,7 @@ func resourceBotChannelWebChatCreate(d *pluginsdk.ResourceData, meta interface{} Kind: botservice.KindBot, } - if _, err := client.Update(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { + if _, err := client.Create(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { return fmt.Errorf("creating %s: %+v", id, err) } @@ -174,13 +196,12 @@ func resourceBotChannelWebChatDelete(d *pluginsdk.ResourceData, meta interface{} return err } - defaultSite := "Default Site" channel := botservice.BotChannel{ Properties: botservice.WebChatChannel{ Properties: &botservice.WebChatChannelProperties{ Sites: &[]botservice.WebChatSite{ { - SiteName: utils.String(defaultSite), + SiteName: utils.String("Default Site"), IsEnabled: utils.Bool(true), }, }, @@ -191,8 +212,10 @@ func resourceBotChannelWebChatDelete(d *pluginsdk.ResourceData, meta interface{} Kind: botservice.KindBot, } + // The Bot WebChat Channel would be created by default while creating Bot Registrations Channel. + // So it has to restore the default Web Chat Channel while deleting if _, err := client.Update(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameWebChatChannel, channel); err != nil { - return fmt.Errorf("deleting %s: %+v", id, err) + return fmt.Errorf("restoring the default Web Chat Channel %s: %+v", id, err) } return nil @@ -236,3 +259,13 @@ func flattenWebChatSites(input *[]botservice.WebChatSite) []interface{} { } return results } + +func includeDefaultWebSite(sites *[]botservice.WebChatSite) bool { + includeDefaultSite := false + for _, site := range *sites { + if *site.SiteName == "Default Site" { + includeDefaultSite = true + } + } + return includeDefaultSite +} diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go index 56fb331c135b..fe4943f0a163 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go @@ -32,6 +32,64 @@ func testAccBotChannelWebChat_basic(t *testing.T) { }) } +func testAccBotChannelWebChat_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_web_chat", "test") + r := BotChannelWebChatResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func testAccBotChannelWebChat_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_web_chat", "test") + r := BotChannelWebChatResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccBotChannelWebChat_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_web_chat", "test") + r := BotChannelWebChatResource{} + + data.ResourceSequentialTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t BotChannelWebChatResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.BotChannelID(state.ID) if err != nil { @@ -56,7 +114,43 @@ resource "azurerm_bot_channel_web_chat" "test" { resource_group_name = azurerm_resource_group.test.name sites { - enabled_preview = true + site_name = "TestSite" + } +} +`, BotChannelsRegistrationResource{}.basicConfig(data)) +} + +func (BotChannelWebChatResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_web_chat" "import" { + bot_name = azurerm_bot_channel_web_chat.test.bot_name + location = azurerm_bot_channel_web_chat.test.location + resource_group_name = azurerm_bot_channel_web_chat.test.resource_group_name + + sites { + site_name = "TestSite" + } +} +`, BotChannelsRegistrationResource{}.basicConfig(data)) +} + +func (BotChannelWebChatResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_web_chat" "test" { + bot_name = azurerm_bot_channels_registration.test.name + location = azurerm_bot_channels_registration.test.location + resource_group_name = azurerm_resource_group.test.name + + sites { + site_name = "TestSite2" + } + + sites { + site_name = "TestSite3" } } `, BotChannelsRegistrationResource{}.basicConfig(data)) diff --git a/website/docs/r/bot_channel_web_chat.markdown b/website/docs/r/bot_channel_web_chat.markdown new file mode 100644 index 000000000000..977aa47ef227 --- /dev/null +++ b/website/docs/r/bot_channel_web_chat.markdown @@ -0,0 +1,83 @@ +--- +subcategory: "Bot" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_bot_channel_web_chat" +description: |- + Manages a Web Chat integration for a Bot Channel +--- + +# azurerm_bot_channel_web_chat + +Manages a Web Chat integration for a Bot Channel + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_bot_channels_registration" "example" { + name = "example-bcr" + location = "global" + resource_group_name = azurerm_resource_group.example.name + sku = "F0" + microsoft_app_id = data.azurerm_client_config.current.service_principal_application_id +} + +resource "azurerm_bot_channel_web_chat" "example" { + bot_name = azurerm_bot_channels_registration.example.name + location = azurerm_bot_channels_registration.example.location + resource_group_name = azurerm_resource_group.example.name + + sites { + site_name = "TestSite" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `resource_group_name` - (Required) The name of the resource group where the Web Chat Channel should be created. Changing this forces a new resource to be created. + +* `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. + +* `bot_name` - (Required) The name of the Bot Resource this channel will be associated with. Changing this forces a new resource to be created. + +* `sites` - (Required) A `sites` block as defined below. + +--- + +A `sites` block supports the following: + +* `site_name` - (Required) The name of the site. + +* `enabled_preview` - (Optional) Is preview version of the Web Chat Channel enabled? + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Web Chat Channel. + +## 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 Web Chat Channel. +* `update` - (Defaults to 30 minutes) Used when updating the Web Chat Channel. +* `read` - (Defaults to 5 minutes) Used when retrieving the Web Chat Channel. +* `delete` - (Defaults to 30 minutes) Used when deleting the Web Chat Channel. + +## Import + +Web Chat Channels can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_bot_channel_web_chat.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.BotService/botServices/botService1/channels/WebChatChannel +``` From e3cf82ae948b72169dc55f7a4a8d482f3081e8a9 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Tue, 20 Jul 2021 11:50:01 +0800 Subject: [PATCH 4/7] update code --- .../internal/services/bot/bot_channel_test.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/azurerm/internal/services/bot/bot_channel_test.go b/azurerm/internal/services/bot/bot_channel_test.go index 26bae6a7adee..7214650601f1 100644 --- a/azurerm/internal/services/bot/bot_channel_test.go +++ b/azurerm/internal/services/bot/bot_channel_test.go @@ -20,14 +20,17 @@ func TestAccBotChannelsRegistration(t *testing.T) { "complete": testAccBotConnection_complete, }, "channel": { - "slackBasic": testAccBotChannelSlack_basic, - "slackUpdate": testAccBotChannelSlack_update, - "msteamsBasic": testAccBotChannelMsTeams_basic, - "msteamsUpdate": testAccBotChannelMsTeams_update, - "directlineBasic": testAccBotChannelDirectline_basic, - "directlineComplete": testAccBotChannelDirectline_complete, - "directlineUpdate": testAccBotChannelDirectline_update, - "webchatBasic": testAccBotChannelWebChat_basic, + "slackBasic": testAccBotChannelSlack_basic, + "slackUpdate": testAccBotChannelSlack_update, + "msteamsBasic": testAccBotChannelMsTeams_basic, + "msteamsUpdate": testAccBotChannelMsTeams_update, + "directlineBasic": testAccBotChannelDirectline_basic, + "directlineComplete": testAccBotChannelDirectline_complete, + "directlineUpdate": testAccBotChannelDirectline_update, + "webchatBasic": testAccBotChannelWebChat_basic, + "webchatComplete": testAccBotChannelWebChat_complete, + "webchatUpdate": testAccBotChannelWebChat_update, + "webchatRequiresImport": testAccBotChannelWebChat_requiresImport, }, "web_app": { "basic": testAccBotWebApp_basic, From ed3b143aec86419885636d688f95a8744e8c7f48 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Tue, 20 Jul 2021 13:02:44 +0800 Subject: [PATCH 5/7] update code --- .../internal/services/bot/bot_channel_web_chat_resource.go | 6 ++++-- .../services/bot/bot_channel_web_chat_resource_test.go | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go index 0ed56f431954..e55c634443e5 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go @@ -13,6 +13,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/bot/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -54,8 +55,9 @@ func resourceBotChannelWebChat() *pluginsdk.Resource { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "site_name": { - Type: pluginsdk.TypeString, - Required: true, + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, }, "enabled_preview": { diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go index fe4943f0a163..1dbae8f02a08 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go @@ -90,7 +90,7 @@ func testAccBotChannelWebChat_update(t *testing.T) { }) } -func (t BotChannelWebChatResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { +func (r BotChannelWebChatResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.BotChannelID(state.ID) if err != nil { return nil, err @@ -120,7 +120,7 @@ resource "azurerm_bot_channel_web_chat" "test" { `, BotChannelsRegistrationResource{}.basicConfig(data)) } -func (BotChannelWebChatResource) requiresImport(data acceptance.TestData) string { +func (r BotChannelWebChatResource) requiresImport(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -133,7 +133,7 @@ resource "azurerm_bot_channel_web_chat" "import" { site_name = "TestSite" } } -`, BotChannelsRegistrationResource{}.basicConfig(data)) +`, r.basic(data)) } func (BotChannelWebChatResource) complete(data acceptance.TestData) string { From c9fafa9ad75b5c25be391dd0cf48684c3cb122a7 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 21 Jul 2021 12:00:46 +0800 Subject: [PATCH 6/7] update code --- .../bot/bot_channel_web_chat_resource.go | 54 +++++++------------ .../bot/bot_channel_web_chat_resource_test.go | 19 ++----- website/docs/r/bot_channel_web_chat.markdown | 10 +--- 3 files changed, 22 insertions(+), 61 deletions(-) diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go index e55c634443e5..5e9db270b891 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource.go @@ -49,22 +49,12 @@ func resourceBotChannelWebChat() *pluginsdk.Resource { ValidateFunc: validate.BotName, }, - "sites": { + "site_names": { Type: pluginsdk.TypeSet, Required: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "site_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "enabled_preview": { - Type: pluginsdk.TypeBool, - Optional: true, - }, - }, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, }, }, }, @@ -92,7 +82,7 @@ func resourceBotChannelWebChatCreate(d *pluginsdk.ResourceData, meta interface{} if props := existing.Properties; props != nil { defaultChannel, ok := props.AsWebChatChannel() if ok && defaultChannel.Properties != nil { - if includeDefaultWebSite(defaultChannel.Properties.Sites) { + if includeDefaultWebChatSite(defaultChannel.Properties.Sites) { if _, err := client.Delete(ctx, id.ResourceGroup, id.BotServiceName, string(botservice.ChannelNameBasicChannelChannelNameWebChatChannel)); err != nil { return fmt.Errorf("deleting the default Web Chat Channel %s: %+v", id, err) } @@ -106,7 +96,7 @@ func resourceBotChannelWebChatCreate(d *pluginsdk.ResourceData, meta interface{} channel := botservice.BotChannel{ Properties: botservice.WebChatChannel{ Properties: &botservice.WebChatChannelProperties{ - Sites: expandWebChatSites(d.Get("sites").(*pluginsdk.Set).List()), + Sites: expandSiteNames(d.Get("site_names").(*pluginsdk.Set).List()), }, ChannelName: botservice.ChannelNameBasicChannelChannelNameWebChatChannel, }, @@ -150,8 +140,8 @@ func resourceBotChannelWebChatRead(d *pluginsdk.ResourceData, meta interface{}) if props := resp.Properties; props != nil { if channel, ok := props.AsWebChatChannel(); ok { if channelProps := channel.Properties; channelProps != nil { - if err := d.Set("sites", flattenWebChatSites(channelProps.Sites)); err != nil { - return fmt.Errorf("setting `sites`: %+v", err) + if err := d.Set("site_names", flattenSiteNames(channelProps.Sites)); err != nil { + return fmt.Errorf("setting `site_names`: %+v", err) } } } @@ -173,7 +163,7 @@ func resourceBotChannelWebChatUpdate(d *pluginsdk.ResourceData, meta interface{} channel := botservice.BotChannel{ Properties: botservice.WebChatChannel{ Properties: &botservice.WebChatChannelProperties{ - Sites: expandWebChatSites(d.Get("sites").(*pluginsdk.Set).List()), + Sites: expandSiteNames(d.Get("site_names").(*pluginsdk.Set).List()), }, ChannelName: botservice.ChannelNameBasicChannelChannelNameWebChatChannel, }, @@ -223,46 +213,38 @@ func resourceBotChannelWebChatDelete(d *pluginsdk.ResourceData, meta interface{} return nil } -func expandWebChatSites(input []interface{}) *[]botservice.WebChatSite { +func expandSiteNames(input []interface{}) *[]botservice.WebChatSite { results := make([]botservice.WebChatSite, 0) - for _, item := range input { - v := item.(map[string]interface{}) + for _, item := range input { results = append(results, botservice.WebChatSite{ - EnablePreview: utils.Bool(v["enabled_preview"].(bool)), - SiteName: utils.String(v["site_name"].(string)), - IsEnabled: utils.Bool(true), + SiteName: utils.String(item.(string)), + IsEnabled: utils.Bool(true), }) } + return &results } -func flattenWebChatSites(input *[]botservice.WebChatSite) []interface{} { +func flattenSiteNames(input *[]botservice.WebChatSite) []interface{} { results := make([]interface{}, 0) if input == nil { return results } for _, item := range *input { - var enablePreview bool - if item.EnablePreview != nil { - enablePreview = *item.EnablePreview - } - var siteName string if item.SiteName != nil { siteName = *item.SiteName } - results = append(results, map[string]interface{}{ - "enabled_preview": enablePreview, - "site_name": siteName, - }) + results = append(results, siteName) } + return results } -func includeDefaultWebSite(sites *[]botservice.WebChatSite) bool { +func includeDefaultWebChatSite(sites *[]botservice.WebChatSite) bool { includeDefaultSite := false for _, site := range *sites { if *site.SiteName == "Default Site" { diff --git a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go index 1dbae8f02a08..e00badce5d6d 100644 --- a/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go +++ b/azurerm/internal/services/bot/bot_channel_web_chat_resource_test.go @@ -112,10 +112,7 @@ resource "azurerm_bot_channel_web_chat" "test" { bot_name = azurerm_bot_channels_registration.test.name location = azurerm_bot_channels_registration.test.location resource_group_name = azurerm_resource_group.test.name - - sites { - site_name = "TestSite" - } + site_names = ["TestSite"] } `, BotChannelsRegistrationResource{}.basicConfig(data)) } @@ -128,10 +125,7 @@ resource "azurerm_bot_channel_web_chat" "import" { bot_name = azurerm_bot_channel_web_chat.test.bot_name location = azurerm_bot_channel_web_chat.test.location resource_group_name = azurerm_bot_channel_web_chat.test.resource_group_name - - sites { - site_name = "TestSite" - } + site_names = ["TestSite"] } `, r.basic(data)) } @@ -144,14 +138,7 @@ resource "azurerm_bot_channel_web_chat" "test" { bot_name = azurerm_bot_channels_registration.test.name location = azurerm_bot_channels_registration.test.location resource_group_name = azurerm_resource_group.test.name - - sites { - site_name = "TestSite2" - } - - sites { - site_name = "TestSite3" - } + site_names = ["TestSite2", "TestSite3"] } `, BotChannelsRegistrationResource{}.basicConfig(data)) } diff --git a/website/docs/r/bot_channel_web_chat.markdown b/website/docs/r/bot_channel_web_chat.markdown index 977aa47ef227..4626b8917f1d 100644 --- a/website/docs/r/bot_channel_web_chat.markdown +++ b/website/docs/r/bot_channel_web_chat.markdown @@ -49,15 +49,7 @@ The following arguments are supported: * `bot_name` - (Required) The name of the Bot Resource this channel will be associated with. Changing this forces a new resource to be created. -* `sites` - (Required) A `sites` block as defined below. - ---- - -A `sites` block supports the following: - -* `site_name` - (Required) The name of the site. - -* `enabled_preview` - (Optional) Is preview version of the Web Chat Channel enabled? +* `site_names` - (Required) A list of Web Chat Sites. ## Attributes Reference From 033c7418e67d2c5cc6b37291f98f442d376319e5 Mon Sep 17 00:00:00 2001 From: neil-yechenwei Date: Wed, 21 Jul 2021 16:33:24 +0800 Subject: [PATCH 7/7] update code --- website/docs/r/bot_channel_web_chat.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/bot_channel_web_chat.markdown b/website/docs/r/bot_channel_web_chat.markdown index 4626b8917f1d..4f2978735985 100644 --- a/website/docs/r/bot_channel_web_chat.markdown +++ b/website/docs/r/bot_channel_web_chat.markdown @@ -49,7 +49,7 @@ The following arguments are supported: * `bot_name` - (Required) The name of the Bot Resource this channel will be associated with. Changing this forces a new resource to be created. -* `site_names` - (Required) A list of Web Chat Sites. +* `site_names` - (Required) A list of Web Chat Site names. ## Attributes Reference