diff --git a/azurerm/internal/services/bot/client.go b/azurerm/internal/services/bot/client.go index 49aff3685b54..a337b6631b04 100644 --- a/azurerm/internal/services/bot/client.go +++ b/azurerm/internal/services/bot/client.go @@ -8,6 +8,7 @@ import ( type Client struct { BotClient *botservice.BotsClient ConnectionClient *botservice.BotConnectionClient + ChannelClient *botservice.ChannelsClient } func BuildClient(o *common.ClientOptions) *Client { @@ -18,8 +19,12 @@ func BuildClient(o *common.ClientOptions) *Client { connectionClient := botservice.NewBotConnectionClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&connectionClient.Client, o.ResourceManagerAuthorizer) + channelClient := botservice.NewChannelsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&channelClient.Client, o.ResourceManagerAuthorizer) + return &Client{ BotClient: &botClient, + ChannelClient: &channelClient, ConnectionClient: &connectionClient, } } diff --git a/azurerm/provider.go b/azurerm/provider.go index 5351fcf65aff..bd4d279ca778 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -195,6 +195,7 @@ func Provider() terraform.ResourceProvider { "azurerm_batch_account": resourceArmBatchAccount(), "azurerm_batch_application": resourceArmBatchApplication(), "azurerm_batch_certificate": resourceArmBatchCertificate(), + "azurerm_bot_channel_slack": resourceArmBotChannelSlack(), "azurerm_bot_channels_registration": resourceArmBotChannelsRegistration(), "azurerm_bot_connection": resourceArmBotConnection(), "azurerm_batch_pool": resourceArmBatchPool(), diff --git a/azurerm/resource_arm_bot_channel_slack.go b/azurerm/resource_arm_bot_channel_slack.go new file mode 100644 index 000000000000..ffe234a8390c --- /dev/null +++ b/azurerm/resource_arm_bot_channel_slack.go @@ -0,0 +1,217 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/preview/botservice/mgmt/2018-07-12/botservice" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "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/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmBotChannelSlack() *schema.Resource { + return &schema.Resource{ + Create: resourceArmBotChannelSlackCreate, + Read: resourceArmBotChannelSlackRead, + Delete: resourceArmBotChannelSlackDelete, + Update: resourceArmBotChannelSlackUpdate, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "bot_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "client_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "verification_token": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "landing_page_url": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + } +} + +func resourceArmBotChannelSlackCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).bot.ChannelClient + ctx := meta.(*ArmClient).StopContext + + 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.ChannelNameSlackChannel), botName) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of creating Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_bot_channel_slack", *existing.ID) + } + } + + channel := botservice.BotChannel{ + Properties: botservice.SlackChannel{ + Properties: &botservice.SlackChannelProperties{ + ClientID: utils.String(d.Get("client_id").(string)), + ClientSecret: utils.String(d.Get("client_secret").(string)), + VerificationToken: utils.String(d.Get("verification_token").(string)), + LandingPageURL: utils.String(d.Get("landing_page_url").(string)), + IsEnabled: utils.Bool(true), + RegisterBeforeOAuthFlow: utils.Bool(true), + }, + ChannelName: botservice.ChannelNameSlackChannel1, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Create(ctx, resourceGroup, botName, botservice.ChannelNameSlackChannel, channel); err != nil { + return fmt.Errorf("Error issuing create request for Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameSlackChannel)) + if err != nil { + return fmt.Errorf("Error making get request for Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + if resp.ID == nil { + return fmt.Errorf("Cannot read Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + d.SetId(*resp.ID) + + return resourceArmBotChannelSlackRead(d, meta) +} + +func resourceArmBotChannelSlackRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).bot.ChannelClient + ctx := meta.(*ArmClient).StopContext + + 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.ChannelNameSlackChannel)) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Channel Slack 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 Slack 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) + + if props := resp.Properties; props != nil { + if channel, ok := props.AsSlackChannel(); ok { + if channelProps := channel.Properties; channelProps != nil { + d.Set("client_id", channelProps.ClientID) + } + } + } + + return nil +} + +func resourceArmBotChannelSlackUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).bot.ChannelClient + ctx := meta.(*ArmClient).StopContext + + botName := d.Get("bot_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + channel := botservice.BotChannel{ + Properties: botservice.SlackChannel{ + Properties: &botservice.SlackChannelProperties{ + ClientID: utils.String(d.Get("client_id").(string)), + ClientSecret: utils.String(d.Get("client_secret").(string)), + VerificationToken: utils.String(d.Get("verification_token").(string)), + LandingPageURL: utils.String(d.Get("landing_page_url").(string)), + IsEnabled: utils.Bool(true), + RegisterBeforeOAuthFlow: utils.Bool(true), + }, + ChannelName: botservice.ChannelNameSlackChannel1, + }, + Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))), + Kind: botservice.KindBot, + } + + if _, err := client.Update(ctx, resourceGroup, botName, botservice.ChannelNameSlackChannel, channel); err != nil { + return fmt.Errorf("Error issuing create request for Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameSlackChannel)) + if err != nil { + return fmt.Errorf("Error making get request for Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + if resp.ID == nil { + return fmt.Errorf("Cannot read Channel Slack for Bot %q (Resource Group %q): %+v", resourceGroup, botName, err) + } + + d.SetId(*resp.ID) + + return resourceArmBotChannelSlackRead(d, meta) +} + +func resourceArmBotChannelSlackDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).bot.ChannelClient + ctx := meta.(*ArmClient).StopContext + + 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.ChannelNameSlackChannel)) + if err != nil { + if !response.WasNotFound(resp.Response) { + return fmt.Errorf("Error deleting Channel Slack for Bot %q (Resource Group %q): %+v", id.ResourceGroup, botName, err) + } + } + + return nil +} diff --git a/azurerm/resource_arm_bot_channel_slack_test.go b/azurerm/resource_arm_bot_channel_slack_test.go new file mode 100644 index 000000000000..874f1e3b8d10 --- /dev/null +++ b/azurerm/resource_arm_bot_channel_slack_test.go @@ -0,0 +1,194 @@ +package azurerm + +import ( + "fmt" + "net/http" + "os" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/preview/botservice/mgmt/2018-07-12/botservice" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func testAccAzureRMBotChannelSlack_basic(t *testing.T) { + if ok := skipSlackChannel(); ok { + t.Skip("Skipping as one of `ARM_TEST_SLACK_CLIENT_ID`, `ARM_TEST_SLACK_CLIENT_SECRET`, or `ARM_TEST_SLACK_VERIFICATION_TOKEN` was not specified") + } + ri := tf.AccRandTimeInt() + config := testAccAzureRMBotChannelSlack_basicConfig(ri, testLocation()) + resourceName := "azurerm_bot_channel_slack.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBotChannelSlackDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelSlackExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "client_secret", + "verification_token", + "landing_page_url", + }, + }, + }, + }) +} + +func testAccAzureRMBotChannelSlack_update(t *testing.T) { + if ok := skipSlackChannel(); ok { + t.Skip("Skipping as one of `ARM_TEST_SLACK_CLIENT_ID`, `ARM_TEST_SLACK_CLIENT_SECRET`, or `ARM_TEST_SLACK_VERIFICATION_TOKEN` was not specified") + } + ri := tf.AccRandTimeInt() + config := testAccAzureRMBotChannelSlack_basicConfig(ri, testLocation()) + config2 := testAccAzureRMBotChannelSlack_basicUpdate(ri, testLocation()) + resourceName := "azurerm_bot_channel_slack.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBotChannelSlackDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelSlackExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "client_secret", + "verification_token", + "landing_page_url", + }, + }, + { + Config: config2, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBotChannelSlackExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "client_secret", + "verification_token", + "landing_page_url", + }, + }, + }, + }) +} + +func testCheckAzureRMBotChannelSlackExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // 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 Slack") + } + + client := testAccProvider.Meta().(*ArmClient).bot.ChannelClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameSlackChannel)) + if err != nil { + return fmt.Errorf("Bad: Get on botChannelClient: %+v", err) + } + + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Bot Channel Slack %q (resource group: %q / bot: %q) does not exist", name, resourceGroup, botName) + } + + return nil + } +} + +func testCheckAzureRMBotChannelSlackDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).bot.ChannelClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_bot_channel_slack" { + continue + } + + botName := rs.Primary.Attributes["bot_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, botName, string(botservice.ChannelNameSlackChannel)) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Bot Channel Slack still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +func testAccAzureRMBotChannelSlack_basicConfig(rInt int, location string) string { + template := testAccAzureRMBotChannelsRegistration_basicConfig(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_slack" "test" { + bot_name = "${azurerm_bot_channels_registration.test.name}" + location = "${azurerm_bot_channels_registration.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + client_id = "%s" + client_secret = "%s" + verification_token = "%s" +} +`, template, os.Getenv("ARM_TEST_SLACK_CLIENT_ID"), os.Getenv("ARM_TEST_SLACK_CLIENT_SECRET"), os.Getenv("ARM_TEST_SLACK_VERIFICATION_TOKEN")) +} + +func testAccAzureRMBotChannelSlack_basicUpdate(rInt int, location string) string { + template := testAccAzureRMBotChannelsRegistration_basicConfig(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_bot_channel_slack" "test" { + bot_name = "${azurerm_bot_channels_registration.test.name}" + location = "${azurerm_bot_channels_registration.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + client_id = "%s" + client_secret = "%s" + verification_token = "%s" + landing_page_url = "http://example.com" +} +`, template, os.Getenv("ARM_TEST_SLACK_CLIENT_ID"), os.Getenv("ARM_TEST_SLACK_CLIENT_SECRET"), os.Getenv("ARM_TEST_SLACK_VERIFICATION_TOKEN")) +} + +func skipSlackChannel() bool { + if os.Getenv("ARM_TEST_SLACK_CLIENT_ID") == "" || os.Getenv("ARM_TEST_SLACK_CLIENT_SECRET") == "" || os.Getenv("ARM_TEST_SLACK_VERIFICATION_TOKEN") == "" { + return true + } + + return false +} diff --git a/azurerm/resource_arm_bot_channels_registration_test.go b/azurerm/resource_arm_bot_channels_registration_test.go index 5da33f715626..63012d912360 100644 --- a/azurerm/resource_arm_bot_channels_registration_test.go +++ b/azurerm/resource_arm_bot_channels_registration_test.go @@ -24,6 +24,10 @@ func TestAccAzureRMBotChannelsRegistration(t *testing.T) { "basic": testAccAzureRMBotConnection_basic, "complete": testAccAzureRMBotConnection_complete, }, + "channel": { + "slackBasic": testAccAzureRMBotChannelSlack_basic, + "slackUpdate": testAccAzureRMBotChannelSlack_update, + }, } for group, m := range testCases { diff --git a/website/azurerm.erb b/website/azurerm.erb index 43997c1e7dc9..38542d1b1858 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -648,6 +648,9 @@