Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: azurerm_bot_channel_alexa #12682

Merged
merged 10 commits into from Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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