diff --git a/azurerm/internal/services/bot/registration.go b/azurerm/internal/services/bot/registration.go index 9fe797ce7ef2..56938060c370 100644 --- a/azurerm/internal/services/bot/registration.go +++ b/azurerm/internal/services/bot/registration.go @@ -29,6 +29,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_bot_channel_email": resourceArmBotChannelEmail(), "azurerm_bot_channel_slack": resourceArmBotChannelSlack(), "azurerm_bot_channel_ms_teams": resourceArmBotChannelMsTeams(), + "azurerm_bot_channel_directline": resourceArmBotChannelDirectline(), "azurerm_bot_channels_registration": resourceArmBotChannelsRegistration(), "azurerm_bot_connection": resourceArmBotConnection(), "azurerm_bot_web_app": resourceArmBotWebApp(), diff --git a/azurerm/internal/services/bot/resource_arm_bot_channel_directline.go b/azurerm/internal/services/bot/resource_arm_bot_channel_directline.go new file mode 100644 index 000000000000..b84927665a03 --- /dev/null +++ b/azurerm/internal/services/bot/resource_arm_bot_channel_directline.go @@ -0,0 +1,369 @@ +package bot + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/botservice/mgmt/2018-07-12/botservice" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "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/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmBotChannelDirectline() *schema.Resource { + return &schema.Resource{ + Create: resourceArmBotChannelDirectlineCreate, + Read: resourceArmBotChannelDirectlineRead, + Delete: resourceArmBotChannelDirectlineDelete, + Update: resourceArmBotChannelDirectlineUpdate, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "bot_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "site": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "v1_allowed": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "v3_allowed": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "enhanced_authentication_enabled": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + + "trusted_origins": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + + "key": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + + "key2": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + + "id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func resourceArmBotChannelDirectlineCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + resourceGroup := d.Get("resource_group_name").(string) + botName := d.Get("bot_name").(string) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, resourceGroup, string(botservice.ChannelNameDirectLineChannel1), botName) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of creating Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_bot_channel_directline", *existing.ID) + } + } + + channel := botservice.BotChannel{ + Properties: botservice.DirectLineChannel{ + Properties: &botservice.DirectLineChannelProperties{ + Sites: expandDirectlineSites(d.Get("site").(*schema.Set).List()), + }, + ChannelName: botservice.ChannelNameDirectLineChannel1, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Create(ctx, resourceGroup, botName, botservice.ChannelNameDirectLineChannel, channel); err != nil { + return fmt.Errorf("Error issuing create request for Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + // Unable to create a new site with enhanced_authentication_enabled in the same operation, so we need to make two calls + if _, err := client.Update(ctx, resourceGroup, botName, botservice.ChannelNameDirectLineChannel, channel); err != nil { + return fmt.Errorf("Error issuing create request for Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameDirectLineChannel1)) + if err != nil { + return fmt.Errorf("Error making get request for Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + if resp.ID == nil { + return fmt.Errorf("Cannot read Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + d.SetId(*resp.ID) + + return resourceArmBotChannelDirectlineRead(d, meta) +} + +func resourceArmBotChannelDirectlineRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + botName := id.Path["botServices"] + resp, err := client.Get(ctx, id.ResourceGroup, botName, string(botservice.ChannelNameDirectLineChannel1)) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Channel Directline for Bot %q (Resource Group %q) was not found - removing from state!", id.ResourceGroup, botName) + d.SetId("") + return nil + } + + return fmt.Errorf("Error reading Channel Directline for Bot %q (Resource Group %q): %+v", id.ResourceGroup, botName, err) + } + + d.Set("resource_group_name", id.ResourceGroup) + d.Set("location", resp.Location) + d.Set("bot_name", botName) + + channelsResp, _ := client.ListWithKeys(ctx, id.ResourceGroup, botName, botservice.ChannelNameDirectLineChannel) + if props := channelsResp.Properties; props != nil { + if channel, ok := props.AsDirectLineChannel(); ok { + if channelProps := channel.Properties; channelProps != nil { + d.Set("site", flattenDirectlineSites(filterSites(channelProps.Sites))) + } + } + } + + return nil +} + +func resourceArmBotChannelDirectlineUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + botName := d.Get("bot_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + channel := botservice.BotChannel{ + Properties: botservice.DirectLineChannel{ + Properties: &botservice.DirectLineChannelProperties{ + Sites: expandDirectlineSites(d.Get("site").(*schema.Set).List()), + }, + ChannelName: botservice.ChannelNameDirectLineChannel1, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Update(ctx, resourceGroup, botName, botservice.ChannelNameDirectLineChannel, channel); err != nil { + return fmt.Errorf("Error issuing create request for Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + // Unable to create a new site with enhanced_authentication_enabled in the same operation, so we need to make two calls + if _, err := client.Update(ctx, resourceGroup, botName, botservice.ChannelNameDirectLineChannel, channel); err != nil { + return fmt.Errorf("Error issuing create request for Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameDirectLineChannel1)) + if err != nil { + return fmt.Errorf("Error making get request for Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + if resp.ID == nil { + return fmt.Errorf("Cannot read Channel Directline for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + d.SetId(*resp.ID) + + return resourceArmBotChannelDirectlineRead(d, meta) +} + +func resourceArmBotChannelDirectlineDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Bot.ChannelClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + botName := id.Path["botServices"] + + resp, err := client.Delete(ctx, id.ResourceGroup, botName, string(botservice.ChannelNameDirectLineChannel1)) + if err != nil { + if !response.WasNotFound(resp.Response) { + return fmt.Errorf("Error deleting Channel Directline for Bot %q (Resource Group %q): %+v", id.ResourceGroup, botName, err) + } + } + + return nil +} + +func expandDirectlineSites(input []interface{}) *[]botservice.DirectLineSite { + sites := make([]botservice.DirectLineSite, len(input)) + + for _, element := range input { + if element == nil { + continue + } + + site := element.(map[string]interface{}) + expanded := botservice.DirectLineSite{} + + if v, ok := site["name"].(string); ok { + expanded.SiteName = &v + } + if v, ok := site["enabled"].(bool); ok { + expanded.IsEnabled = &v + } + if v, ok := site["v1_allowed"].(bool); ok { + expanded.IsV1Enabled = &v + } + if v, ok := site["v3_allowed"].(bool); ok { + expanded.IsV3Enabled = &v + } + if v, ok := site["enhanced_authentication_enabled"].(bool); ok { + expanded.IsSecureSiteEnabled = &v + } + if v, ok := site["trusted_origins"].(*schema.Set); ok { + origins := v.List() + items := make([]string, len(origins)) + for i, raw := range origins { + items[i] = raw.(string) + } + expanded.TrustedOrigins = &items + } + + sites = append(sites, expanded) + } + + return &sites +} + +func flattenDirectlineSites(input []botservice.DirectLineSite) []interface{} { + sites := make([]interface{}, len(input)) + + for i, element := range input { + site := make(map[string]interface{}) + + if v := element.SiteName; v != nil { + site["name"] = *v + } + + if element.Key != nil { + site["key"] = *element.Key + } + + if element.Key2 != nil { + site["key2"] = *element.Key2 + } + + if element.IsEnabled != nil { + site["enabled"] = *element.IsEnabled + } + + if element.IsV1Enabled != nil { + site["v1_allowed"] = *element.IsV1Enabled + } + + if element.IsV3Enabled != nil { + site["v3_allowed"] = *element.IsV3Enabled + } + + if element.IsSecureSiteEnabled != nil { + site["enhanced_authentication_enabled"] = *element.IsSecureSiteEnabled + } + + if element.TrustedOrigins != nil { + site["trusted_origins"] = *element.TrustedOrigins + } + + sites[i] = site + } + + return sites +} + +// When creating a new directline channel, a Default Site is created +// There is a race condition where this site is not removed before the create request is completed +func filterSites(sites *[]botservice.DirectLineSite) []botservice.DirectLineSite { + filtered := make([]botservice.DirectLineSite, 0) + for _, site := range *sites { + if *site.SiteName == "Default Site" { + continue + } + filtered = append(filtered, site) + } + return filtered +} diff --git a/azurerm/internal/services/bot/tests/resource_arm_bot_channel_directline_test.go b/azurerm/internal/services/bot/tests/resource_arm_bot_channel_directline_test.go new file mode 100644 index 000000000000..94c210e8eabf --- /dev/null +++ b/azurerm/internal/services/bot/tests/resource_arm_bot_channel_directline_test.go @@ -0,0 +1,196 @@ +package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/preview/botservice/mgmt/2018-07-12/botservice" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func testAccAzureRMBotChannelDirectline_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_directline", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMBotChannelDirectlineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBotChannelDirectline_basicConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelDirectlineExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMBotChannelDirectline_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_directline", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMBotChannelDirectlineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBotChannelDirectline_completeConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelDirectlineExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testAccAzureRMBotChannelDirectline_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_bot_channel_directline", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMBotChannelDirectlineDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBotChannelDirectline_basicConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelDirectlineExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMBotChannelDirectline_completeConfig(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelDirectlineExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMBotChannelDirectline_basicUpdate(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelDirectlineExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMBotChannelDirectlineExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Bot.ChannelClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + botName := rs.Primary.Attributes["bot_name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Bot Channel Directline") + } + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameDirectLineChannel1)) + if err != nil { + return fmt.Errorf("Bad: Get on botChannelClient: %+v", err) + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Bot Channel Directline %q (resource group: %q / bot: %q) does not exist", name, resourceGroup, botName) + } + + return nil + } +} + +func testCheckAzureRMBotChannelDirectlineDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Bot.ChannelClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_bot_channel_directline" { + continue + } + + botName := rs.Primary.Attributes["bot_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameDirectLineChannel1)) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Bot Channel Directline still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +func testAccAzureRMBotChannelDirectline_basicConfig(data acceptance.TestData) string { + template := testAccAzureRMBotChannelsRegistration_basicConfig(data) + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_directline" "test" { + bot_name = "${azurerm_bot_channels_registration.test.name}" + location = "${azurerm_bot_channels_registration.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + site { + name = "test" + enabled = true + } +} +`, template) +} + +func testAccAzureRMBotChannelDirectline_completeConfig(data acceptance.TestData) string { + template := testAccAzureRMBotChannelsRegistration_basicConfig(data) + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_directline" "test" { + bot_name = "${azurerm_bot_channels_registration.test.name}" + location = "${azurerm_bot_channels_registration.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + site { + name = "test" + enabled = true + v1_allowed = true + v3_allowed = true + enhanced_authentication_enabled = true + trusted_origins = ["https://example.com"] + } +} +`, template) +} + +func testAccAzureRMBotChannelDirectline_basicUpdate(data acceptance.TestData) string { + template := testAccAzureRMBotChannelsRegistration_basicConfig(data) + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_directline" "test" { + bot_name = "${azurerm_bot_channels_registration.test.name}" + location = "${azurerm_bot_channels_registration.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + site { + name = "test" + enabled = false + } +} +`, template) +} diff --git a/azurerm/internal/services/bot/tests/resource_arm_bot_channels_registration_test.go b/azurerm/internal/services/bot/tests/resource_arm_bot_channels_registration_test.go index 7181a10d1f73..3e5ddcac3584 100644 --- a/azurerm/internal/services/bot/tests/resource_arm_bot_channels_registration_test.go +++ b/azurerm/internal/services/bot/tests/resource_arm_bot_channels_registration_test.go @@ -26,10 +26,13 @@ func TestAccAzureRMBotChannelsRegistration(t *testing.T) { "complete": testAccAzureRMBotConnection_complete, }, "channel": { - "slackBasic": testAccAzureRMBotChannelSlack_basic, - "slackUpdate": testAccAzureRMBotChannelSlack_update, - "msteamsBasic": testAccAzureRMBotChannelMsTeams_basic, - "msteamsUpdate": testAccAzureRMBotChannelMsTeams_update, + "slackBasic": testAccAzureRMBotChannelSlack_basic, + "slackUpdate": testAccAzureRMBotChannelSlack_update, + "msteamsBasic": testAccAzureRMBotChannelMsTeams_basic, + "msteamsUpdate": testAccAzureRMBotChannelMsTeams_update, + "directlineBasic": testAccAzureRMBotChannelDirectline_basic, + "directlineComplete": testAccAzureRMBotChannelDirectline_complete, + "directlineUpdate": testAccAzureRMBotChannelDirectline_update, }, "web_app": { "basic": testAccAzureRMBotWebApp_basic, diff --git a/website/azurerm.erb b/website/azurerm.erb index 58330f5ec66e..53ffe06eec70 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -878,6 +878,10 @@ azurerm_bot_channel_slack +
  • + azurerm_bot_channel_directline +
  • +
  • azurerm_bot_channels_registration
  • diff --git a/website/docs/r/bot_channel_directline.markdown b/website/docs/r/bot_channel_directline.markdown new file mode 100644 index 000000000000..5ac64028af91 --- /dev/null +++ b/website/docs/r/bot_channel_directline.markdown @@ -0,0 +1,92 @@ +--- +subcategory: "Bot" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_bot_channel_directline" +description: |- + Manages an Directline integration for a Bot Channel +--- + +# azurerm_bot_connection + +Manages a Directline integration for a Bot Channel + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "northeurope" +} + +resource "azurerm_bot_channels_registration" "example" { + name = "example" + 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_directline" "example" { + bot_name = "${azurerm_bot_channels_registration.example.name}" + location = "${azurerm_bot_channels_registration.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" + + site { + name = "default" + enabled = true + } +} +``` + +## Argument Reference + +The following arguments are supported: + +- `resource_group_name` - (Required) The name of the resource group in which to create the Bot Channel. Changing this forces a new resource to be created. + +- `location` - (Required) 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. + +- `site` - (Optional) A site represents a client application that you want to connect to your bot. Multiple `site` blocks may be defined as below + +A `site` block has the following properties: + +- `name` - (Required) The name of the site + +- `enabled` - (Optional) Enables/Disables this site. Enabled by default + +- `v1_allowed` - (Optional) Enables v1 of the Directline protocol for this site. Enabled by default + +- `v3_allowed` - (Optional) Enables v3 of the Directline protocol for this site. Enabled by default + +- `enhanced_authentication_enabled` - (Optional) Enables additional security measures for this site, see [Enhanced Directline Authentication Features](https://blog.botframework.com/2018/09/25/enhanced-direct-line-authentication-features). Disabled by default. + +- `trusted_origins` - (Optional) This field is required when `is_secure_site_enabled` is enabled. Determines which origins can establish a Directline conversation for this site. + + +## Attributes Reference + +The following attributes are exported: + +- `id` - The Bot Channel ID. + +--- + +A `site` block exports the following: + +- `key` - Primary key for accessing this site + +- `key2` - Secondary key for accessing this site + +- `id` - Id for the site + +## Import + +The Directline Channel for a Bot can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_bot_channel_directline.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example/providers/Microsoft.BotService/botServices/example/channels/DirectlineChannel +```