Skip to content

Commit

Permalink
New Resource: azurerm_bot_channel_alexa (#12682)
Browse files Browse the repository at this point in the history
  • Loading branch information
Neil Ye committed Jul 29, 2021
1 parent 8b2b4e0 commit bef2a68
Show file tree
Hide file tree
Showing 5 changed files with 436 additions and 0 deletions.
234 changes: 234 additions & 0 deletions azurerm/internal/services/bot/bot_channel_alexa_resource.go
@@ -0,0 +1,234 @@
package bot

import (
"context"
"fmt"
"log"
"strconv"
"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/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"
"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"
)

func resourceBotChannelAlexa() *pluginsdk.Resource {
return &pluginsdk.Resource{
Create: resourceBotChannelAlexaCreate,
Read: resourceBotChannelAlexaRead,
Delete: resourceBotChannelAlexaDelete,
Update: resourceBotChannelAlexaUpdate,

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,
},

"skill_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringIsNotEmpty,
},
},
}
}

func resourceBotChannelAlexaCreate(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.ChannelNameAlexaChannel))
if d.IsNewResource() {
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 existing %s: %+v", id, err)
}
}
if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_bot_channel_alexa", id.ID())
}
}

channel := botservice.BotChannel{
Properties: botservice.AlexaChannel{
Properties: &botservice.AlexaChannelProperties{
AlexaSkillID: utils.String(d.Get("skill_id").(string)),
IsEnabled: utils.Bool(true),
},
ChannelName: botservice.ChannelNameBasicChannelChannelNameAlexaChannel,
},
Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))),
Kind: botservice.KindBot,
}

if _, err := client.Create(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameAlexaChannel, channel); err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}

// Issue: https://github.com/Azure/azure-rest-api-specs/issues/15298
// There is a long running issue on updating the Alexa Channel.
// The Bot Channel API cannot update Skill ID immediately after the Alexa Channel is created.
// It has to wait a while after created. Then the skill ID can be updated successfully.
stateConf := &pluginsdk.StateChangeConf{
Delay: 5 * time.Minute,
Pending: []string{"204", "404"},
Target: []string{"200", "202"},
Refresh: botChannelAlexaStateRefreshFunc(ctx, client, id),
MinTimeout: 15 * time.Second,
Timeout: d.Timeout(pluginsdk.TimeoutCreate),
}

if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for creation of %s: %+v", id, err)
}

d.SetId(id.ID())
return resourceBotChannelAlexaRead(d, meta)
}

func resourceBotChannelAlexaRead(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.ChannelNameAlexaChannel))
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[DEBUG] %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.AsAlexaChannel(); ok {
if channelProps := channel.Properties; channelProps != nil {
d.Set("skill_id", channelProps.AlexaSkillID)
}
}
}

return nil
}

func resourceBotChannelAlexaUpdate(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.AlexaChannel{
Properties: &botservice.AlexaChannelProperties{
AlexaSkillID: utils.String(d.Get("skill_id").(string)),
IsEnabled: utils.Bool(true),
},
ChannelName: botservice.ChannelNameBasicChannelChannelNameAlexaChannel,
},
Location: utils.String(azure.NormalizeLocation(d.Get("location").(string))),
Kind: botservice.KindBot,
}

_, err = client.Update(ctx, id.ResourceGroup, id.BotServiceName, botservice.ChannelNameAlexaChannel, channel)
if err != nil {
return fmt.Errorf("updating %s: %+v", *id, err)
}

// Issue: https://github.com/Azure/azure-rest-api-specs/issues/15298
// There is a long running issue on updating the Alexa Channel.
// The Bot Channel API cannot update Skill ID immediately after the Alexa Channel is updated.
// It has to wait a while after updated. Then the skill ID can be updated successfully.
stateConf := &pluginsdk.StateChangeConf{
Delay: 5 * time.Minute,
Pending: []string{"204", "404"},
Target: []string{"200", "202"},
Refresh: botChannelAlexaStateRefreshFunc(ctx, client, *id),
MinTimeout: 15 * time.Second,
Timeout: d.Timeout(pluginsdk.TimeoutUpdate),
}

if _, err := stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for update of %s: %+v", id, err)
}

return resourceBotChannelAlexaRead(d, meta)
}

func resourceBotChannelAlexaDelete(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.ChannelNameAlexaChannel))
if err != nil {
if !response.WasNotFound(resp.Response) {
return fmt.Errorf("deleting %s: %+v", *id, err)
}
}

return nil
}

func botChannelAlexaStateRefreshFunc(ctx context.Context, client *botservice.ChannelsClient, id parse.BotChannelId) pluginsdk.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := client.Get(ctx, id.ResourceGroup, id.BotServiceName, id.ChannelName)
if err != nil {
if !utils.ResponseWasNotFound(resp.Response) {
return nil, "", fmt.Errorf("retrieving %s: %+v", id, err)
}
}

return resp, strconv.Itoa(resp.StatusCode), nil
}
}
124 changes: 124 additions & 0 deletions azurerm/internal/services/bot/bot_channel_alexa_resource_test.go
@@ -0,0 +1,124 @@
package bot_test

import (
"context"
"fmt"
"testing"

"github.com/Azure/azure-sdk-for-go/services/botservice/mgmt/2021-03-01/botservice"
"github.com/google/uuid"
"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 BotChannelAlexaResource struct {
}

func testAccBotChannelAlexa_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_bot_channel_alexa", "test")
r := BotChannelAlexaResource{}

data.ResourceSequentialTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func testAccBotChannelAlexa_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_bot_channel_alexa", "test")
r := BotChannelAlexaResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.RequiresImportErrorStep(r.requiresImport),
})
}

func testAccBotChannelAlexa_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_bot_channel_alexa", "test")
r := BotChannelAlexaResource{}

data.ResourceSequentialTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.update(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (t BotChannelAlexaResource) 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.ChannelNameAlexaChannel))
if err != nil {
return nil, fmt.Errorf("retrieving %s: %v", id.String(), err)
}

return utils.Bool(resp.Properties != nil), nil
}

func (BotChannelAlexaResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_bot_channel_alexa" "test" {
bot_name = azurerm_bot_channels_registration.test.name
location = azurerm_bot_channels_registration.test.location
resource_group_name = azurerm_resource_group.test.name
skill_id = "amzn1.ask.skill.%s"
}
`, BotChannelsRegistrationResource{}.basicConfig(data), uuid.New().String())
}

func (r BotChannelAlexaResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_bot_channel_alexa" "import" {
bot_name = azurerm_bot_channel_alexa.test.bot_name
location = azurerm_bot_channel_alexa.test.location
resource_group_name = azurerm_bot_channel_alexa.test.resource_group_name
skill_id = azurerm_bot_channel_alexa.test.skill_id
}
`, r.basic(data))
}

func (BotChannelAlexaResource) update(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_bot_channel_alexa" "test" {
bot_name = azurerm_bot_channels_registration.test.name
location = azurerm_bot_channels_registration.test.location
resource_group_name = azurerm_resource_group.test.name
skill_id = "amzn1.ask.skill.%s"
}
`, BotChannelsRegistrationResource{}.basicConfig(data), uuid.New().String())
}
3 changes: 3 additions & 0 deletions azurerm/internal/services/bot/bot_channel_test.go
Expand Up @@ -20,6 +20,9 @@ func TestAccBotChannelsRegistration(t *testing.T) {
"complete": testAccBotConnection_complete,
},
"channel": {
"alexaBasic": testAccBotChannelAlexa_basic,
"alexaUpdate": testAccBotChannelAlexa_update,
"alexaRequiresImport": testAccBotChannelAlexa_requiresImport,
"slackBasic": testAccBotChannelSlack_basic,
"slackUpdate": testAccBotChannelSlack_update,
"smsBasic": testAccBotChannelSMS_basic,
Expand Down
1 change: 1 addition & 0 deletions azurerm/internal/services/bot/registration.go
Expand Up @@ -26,6 +26,7 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource {
// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
return map[string]*pluginsdk.Resource{
"azurerm_bot_channel_alexa": resourceBotChannelAlexa(),
"azurerm_bot_channel_directline": resourceBotChannelDirectline(),
"azurerm_bot_channel_email": resourceBotChannelEmail(),
"azurerm_bot_channel_facebook": resourceBotChannelFacebook(),
Expand Down

0 comments on commit bef2a68

Please sign in to comment.