diff --git a/.changelog/38630.txt b/.changelog/38630.txt new file mode 100644 index 00000000000..9739b91b3ce --- /dev/null +++ b/.changelog/38630.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_chatbot_teams_channel_configuration +``` + +```release-note:note +resource/aws_chatbot_teams_channel_configuration: This resource is provided on a best-effort basis, and we welcome the community's help in testing it. +``` \ No newline at end of file diff --git a/docs/acc-test-environment-variables.md b/docs/acc-test-environment-variables.md index 22c243e29d5..af737667441 100644 --- a/docs/acc-test-environment-variables.md +++ b/docs/acc-test-environment-variables.md @@ -64,8 +64,11 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi | `AWS_THIRD_SECRET_ACCESS_KEY` | AWS secret access key with access to a third AWS account for tests requiring multiple accounts. Requires `AWS_THIRD_ACCESS_KEY_ID`. Conflicts with `AWS_THIRD_PROFILE`. | | `AWS_THIRD_PROFILE` | AWS profile with access to a third AWS account for tests requiring multiple accounts. Conflicts with `AWS_THIRD_ACCESS_KEY_ID` and `AWS_THIRD_SECRET_ACCESS_KEY`. | | `AWS_THIRD_REGION` | Third AWS region for tests requiring multiple regions. Defaults to `us-east-2`. | -| `CHATBOT_SLACK_TEAM_ID` | ID of the Slack workspace authorized with AWS Chatbot. | | `CHATBOT_SLACK_CHANNEL_ID` | ID of the Slack channel. | +| `CHATBOT_SLACK_TEAM_ID` | ID of the Slack workspace authorized with AWS Chatbot. | +| `CHATBOT_TEAMS_CHANNEL_ID` | ID of the Microsoft Teams channel. | +| `CHATBOT_TEAMS_TEAM_ID` | ID of the Microsoft Teams workspace authorized with AWS Chatbot. | +| `CHATBOT_TEAMS_TENANT_ID` | ID of the Microsoft Teams tenant. | | `DX_CONNECTION_ID` | Identifier for Direct Connect Connection testing. | | `DX_VIRTUAL_INTERFACE_ID` | Identifier for Direct Connect Virtual Interface testing. | | `EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT` | EC2 Quota for Rules per Security Group. Defaults to 50. **DEPRECATED:** Can be augmented or replaced with Service Quotas lookup. | diff --git a/internal/service/chatbot/consts.go b/internal/service/chatbot/consts.go index 91c38c878b5..818cfa68de0 100644 --- a/internal/service/chatbot/consts.go +++ b/internal/service/chatbot/consts.go @@ -5,4 +5,5 @@ package chatbot const ( ResNameSlackChannelConfiguration = "Slack Channel Configuration" + ResNameTeamsChannelConfiguration = "Teams Channel Configuration" ) diff --git a/internal/service/chatbot/exports_test.go b/internal/service/chatbot/exports_test.go index 8a0847c7bea..e043e520d17 100644 --- a/internal/service/chatbot/exports_test.go +++ b/internal/service/chatbot/exports_test.go @@ -6,6 +6,8 @@ package chatbot // Exports for use in tests only. var ( ResourceSlackChannelConfiguration = newSlackChannelConfigurationResource + ResourceTeamsChannelConfiguration = newTeamsChannelConfigurationResource - FindSlackChannelConfigurationByARN = findSlackChannelConfigurationByARN + FindSlackChannelConfigurationByARN = findSlackChannelConfigurationByARN + FindTeamsChannelConfigurationByTeamID = findTeamsChannelConfigurationByTeamID ) diff --git a/internal/service/chatbot/service_package_gen.go b/internal/service/chatbot/service_package_gen.go index 06adc20feab..3a935f3594e 100644 --- a/internal/service/chatbot/service_package_gen.go +++ b/internal/service/chatbot/service_package_gen.go @@ -30,6 +30,13 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic IdentifierAttribute: "chat_configuration_arn", }, }, + { + Factory: newTeamsChannelConfigurationResource, + Name: "Teams Channel Configuration", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "chat_configuration_arn", + }, + }, } } diff --git a/internal/service/chatbot/teams_channel_configuration.go b/internal/service/chatbot/teams_channel_configuration.go new file mode 100644 index 00000000000..8fe51bfe4ec --- /dev/null +++ b/internal/service/chatbot/teams_channel_configuration.go @@ -0,0 +1,435 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package chatbot + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/chatbot" + awstypes "github.com/aws/aws-sdk-go-v2/service/chatbot/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Teams Channel Configuration") +// @Tags(identifierAttribute="chat_configuration_arn") +func newTeamsChannelConfigurationResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &teamsChannelConfigurationResource{} + + r.SetDefaultCreateTimeout(20 * time.Minute) + r.SetDefaultUpdateTimeout(20 * time.Minute) + r.SetDefaultDeleteTimeout(20 * time.Minute) + + return r, nil +} + +type teamsChannelConfigurationResource struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *teamsChannelConfigurationResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { // nosemgrep:ci.meta-in-func-name + response.TypeName = "aws_chatbot_teams_channel_configuration" +} + +func (r *teamsChannelConfigurationResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "channel_id": schema.StringAttribute{ + Required: true, + }, + "channel_name": schema.StringAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "chat_configuration_arn": framework.ARNAttributeComputedOnly(), + "configuration_name": schema.StringAttribute{ + Required: true, + }, + "guardrail_policy_arns": schema.ListAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + names.AttrIAMRoleARN: schema.StringAttribute{ + Required: true, + }, + "logging_level": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[loggingLevel](), + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(loggingLevelNone)), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "sns_topic_arns": schema.ListAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "team_id": schema.StringAttribute{ + Required: true, + }, + "team_name": schema.StringAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "tenant_id": schema.StringAttribute{ + Required: true, + }, + "user_authorization_required": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + }, + Blocks: map[string]schema.Block{ + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *teamsChannelConfigurationResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data teamsChannelConfigurationResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ChatbotClient(ctx) + + input := &chatbot.CreateMicrosoftTeamsChannelConfigurationInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + if response.Diagnostics.HasError() { + return + } + + input.Tags = getTagsIn(ctx) + + out, err := conn.CreateMicrosoftTeamsChannelConfiguration(ctx, input) + if err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionCreating, ResNameTeamsChannelConfiguration, data.TeamID.ValueString(), err) + + return + } + + output, err := waitTeamsChannelConfigurationAvailable(ctx, conn, aws.ToString(out.ChannelConfiguration.TeamId), r.CreateTimeout(ctx, data.Timeouts)) + if err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionWaitingForCreation, ResNameTeamsChannelConfiguration, aws.ToString(out.ChannelConfiguration.TeamId), err) + + return + } + + // Set values for unknowns + data.ChatConfigurationARN = fwflex.StringToFramework(ctx, output.ChatConfigurationArn) + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *teamsChannelConfigurationResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data teamsChannelConfigurationResourceModel + + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ChatbotClient(ctx) + + output, err := findTeamsChannelConfigurationByTeamID(ctx, conn, data.TeamID.ValueString()) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionReading, ResNameTeamsChannelConfiguration, data.TeamID.ValueString(), err) + return + } + + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *teamsChannelConfigurationResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new teamsChannelConfigurationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ChatbotClient(ctx) + + if teamsChannelConfigurationHasChanges(ctx, new, old) { + input := &chatbot.UpdateMicrosoftTeamsChannelConfigurationInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...) + if response.Diagnostics.HasError() { + return + } + + _, err := conn.UpdateMicrosoftTeamsChannelConfiguration(ctx, input) + if err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionUpdating, ResNameTeamsChannelConfiguration, new.TeamID.ValueString(), err) + + return + } + + if _, err := waitTeamsChannelConfigurationAvailable(ctx, conn, old.TeamID.ValueString(), r.UpdateTimeout(ctx, new.Timeouts)); err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionWaitingForUpdate, ResNameTeamsChannelConfiguration, new.TeamID.ValueString(), err) + + return + } + } + + output, err := findTeamsChannelConfigurationByTeamID(ctx, conn, old.TeamID.ValueString()) + if err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionReading, ResNameTeamsChannelConfiguration, old.TeamID.ValueString(), err) + + return + } + + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &new)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *teamsChannelConfigurationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data teamsChannelConfigurationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ChatbotClient(ctx) + + tflog.Debug(ctx, "deleting Chatbot Teams Channel Configuration", map[string]interface{}{ + "team_id": data.TeamID.ValueString(), + "chat_configuration_arn": data.ChatConfigurationARN.ValueString(), + }) + + input := &chatbot.DeleteMicrosoftTeamsChannelConfigurationInput{ + ChatConfigurationArn: aws.String(data.ChatConfigurationARN.ValueString()), + } + + _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, r.DeleteTimeout(ctx, data.Timeouts), func() (interface{}, error) { + return conn.DeleteMicrosoftTeamsChannelConfiguration(ctx, input) + }, "DependencyViolation") + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionDeleting, ResNameTeamsChannelConfiguration, data.TeamID.ValueString(), err) + return + } + + if _, err := waitTeamsChannelConfigurationDeleted(ctx, conn, data.TeamID.ValueString(), r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + create.AddError(&response.Diagnostics, names.Chatbot, create.ErrActionWaitingForDeletion, ResNameTeamsChannelConfiguration, data.TeamID.ValueString(), err) + return + } +} + +func (r *teamsChannelConfigurationResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func (r *teamsChannelConfigurationResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("team_id"), request, response) +} + +func findTeamsChannelConfiguration(ctx context.Context, conn *chatbot.Client, input *chatbot.ListMicrosoftTeamsChannelConfigurationsInput) (*awstypes.TeamsChannelConfiguration, error) { + output, err := findTeamsChannelConfigurations(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findTeamsChannelConfigurations(ctx context.Context, conn *chatbot.Client, input *chatbot.ListMicrosoftTeamsChannelConfigurationsInput) ([]awstypes.TeamsChannelConfiguration, error) { + var output []awstypes.TeamsChannelConfiguration + + pages := chatbot.NewListMicrosoftTeamsChannelConfigurationsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.TeamChannelConfigurations...) + } + + return output, nil +} + +func findTeamsChannelConfigurationByTeamID(ctx context.Context, conn *chatbot.Client, teamID string) (*awstypes.TeamsChannelConfiguration, error) { + input := &chatbot.ListMicrosoftTeamsChannelConfigurationsInput{ + TeamId: aws.String(teamID), + } + + return findTeamsChannelConfiguration(ctx, conn, input) +} + +const ( + teamsChannelConfigurationAvailable = "AVAILABLE" +) + +func statusTeamsChannelConfiguration(ctx context.Context, conn *chatbot.Client, teamID string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findTeamsChannelConfigurationByTeamID(ctx, conn, teamID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + if err != nil { + return nil, "", err + } + + return output, teamsChannelConfigurationAvailable, nil + } +} + +func waitTeamsChannelConfigurationAvailable(ctx context.Context, conn *chatbot.Client, teamID string, timeout time.Duration) (*awstypes.TeamsChannelConfiguration, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{}, + Target: []string{teamsChannelConfigurationAvailable}, + Refresh: statusTeamsChannelConfiguration(ctx, conn, teamID), + Timeout: timeout, + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.TeamsChannelConfiguration); ok { + return output, err + } + + return nil, err +} + +func waitTeamsChannelConfigurationDeleted(ctx context.Context, conn *chatbot.Client, teamID string, timeout time.Duration) (*awstypes.TeamsChannelConfiguration, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{teamsChannelConfigurationAvailable}, + Target: []string{}, + Refresh: statusTeamsChannelConfiguration(ctx, conn, teamID), + Timeout: timeout, + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.TeamsChannelConfiguration); ok { + return output, err + } + + return nil, err +} + +type teamsChannelConfigurationResourceModel struct { + ChannelID types.String `tfsdk:"channel_id"` + ChannelName types.String `tfsdk:"channel_name"` + ChatConfigurationARN types.String `tfsdk:"chat_configuration_arn"` + ConfigurationName types.String `tfsdk:"configuration_name"` + GuardrailPolicyARNs types.List `tfsdk:"guardrail_policy_arns"` + IAMRoleARN types.String `tfsdk:"iam_role_arn"` + LoggingLevel fwtypes.StringEnum[loggingLevel] `tfsdk:"logging_level"` + SNSTopicARNs types.List `tfsdk:"sns_topic_arns"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + TeamID types.String `tfsdk:"team_id"` + TeamName types.String `tfsdk:"team_name"` + TenantID types.String `tfsdk:"tenant_id"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + UserAuthorizationRequired types.Bool `tfsdk:"user_authorization_required"` +} + +func teamsChannelConfigurationHasChanges(_ context.Context, plan, state teamsChannelConfigurationResourceModel) bool { + return !plan.ChannelID.Equal(state.ChannelID) || + !plan.ChannelName.Equal(state.ChannelName) || + !plan.ChatConfigurationARN.Equal(state.ChatConfigurationARN) || + !plan.ConfigurationName.Equal(state.ConfigurationName) || + !plan.GuardrailPolicyARNs.Equal(state.GuardrailPolicyARNs) || + !plan.IAMRoleARN.Equal(state.IAMRoleARN) || + !plan.LoggingLevel.Equal(state.LoggingLevel) || + !plan.SNSTopicARNs.Equal(state.SNSTopicARNs) || + !plan.TeamID.Equal(state.TeamID) || + !plan.TeamName.Equal(state.TeamName) || + !plan.TenantID.Equal(state.TenantID) || + !plan.UserAuthorizationRequired.Equal(state.UserAuthorizationRequired) +} diff --git a/internal/service/chatbot/teams_channel_configuration_test.go b/internal/service/chatbot/teams_channel_configuration_test.go new file mode 100644 index 00000000000..af1b8d12f17 --- /dev/null +++ b/internal/service/chatbot/teams_channel_configuration_test.go @@ -0,0 +1,212 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package chatbot_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/chatbot/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfchatbot "github.com/hashicorp/terraform-provider-aws/internal/service/chatbot" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +const ( + testResourceTeamsChannelConfiguration = "aws_chatbot_teams_channel_configuration.test" + + envTeamsChannelID = "CHATBOT_TEAMS_CHANNEL_ID" + envTeamsTeamID = "CHATBOT_TEAMS_TEAM_ID" + envTeamsTenantID = "CHATBOT_TEAMS_TENANT_ID" +) + +func TestAccChatbotTeamsChannelConfiguration_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]func(t *testing.T){ + acctest.CtBasic: testAccTeamsChannelConfiguration_basic, + acctest.CtDisappears: testAccTeamsChannelConfiguration_disappears, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccTeamsChannelConfiguration_basic(t *testing.T) { + ctx := acctest.Context(t) + + var teamschannelconfiguration types.TeamsChannelConfiguration + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + // The teams workspace must be created via the AWS Console. It cannot be created via APIs or Terraform. + // Once it is created, export the name of the workspace in the env variable for this test + teamID := acctest.SkipIfEnvVarNotSet(t, envTeamsTeamID) + channelID := acctest.SkipIfEnvVarNotSet(t, envTeamsChannelID) + tenantID := acctest.SkipIfEnvVarNotSet(t, envTeamsTenantID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ChatbotServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTeamsChannelConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTeamsChannelConfigurationConfig_basic(rName, channelID, teamID, tenantID), + Check: resource.ComposeTestCheckFunc( + testAccCheckTeamsChannelConfigurationExists(ctx, testResourceTeamsChannelConfiguration, &teamschannelconfiguration), + resource.TestCheckResourceAttr(testResourceTeamsChannelConfiguration, "configuration_name", rName), + acctest.MatchResourceAttrGlobalARN(testResourceTeamsChannelConfiguration, "chat_configuration_arn", "chatbot", regexache.MustCompile(fmt.Sprintf(`chat-configuration/.*/%s`, rName))), + resource.TestCheckResourceAttrPair(testResourceTeamsChannelConfiguration, names.AttrIAMRoleARN, "aws_iam_role.test", names.AttrARN), + resource.TestCheckResourceAttr(testResourceTeamsChannelConfiguration, "channel_id", channelID), + resource.TestCheckResourceAttrSet(testResourceTeamsChannelConfiguration, "channel_name"), + resource.TestCheckResourceAttr(testResourceTeamsChannelConfiguration, "team_id", teamID), + resource.TestCheckResourceAttrSet(testResourceTeamsChannelConfiguration, "team_name"), + ), + }, + { + ResourceName: testResourceTeamsChannelConfiguration, + ImportState: true, + ImportStateIdFunc: testAccTeamsChannelConfigurationImportStateIDFunc(testResourceTeamsChannelConfiguration), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "team_id", + }, + }, + }) +} + +func testAccTeamsChannelConfiguration_disappears(t *testing.T) { + ctx := acctest.Context(t) + + var teamschannelconfiguration types.TeamsChannelConfiguration + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + // The teams workspace must be created via the AWS Console. It cannot be created via APIs or Terraform. + // Once it is created, export the name of the workspace in the env variable for this test + teamID := acctest.SkipIfEnvVarNotSet(t, envTeamsTeamID) + channelID := acctest.SkipIfEnvVarNotSet(t, envTeamsChannelID) + tenantID := acctest.SkipIfEnvVarNotSet(t, envTeamsTenantID) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ChatbotServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTeamsChannelConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTeamsChannelConfigurationConfig_basic(rName, channelID, teamID, tenantID), + Check: resource.ComposeTestCheckFunc( + testAccCheckTeamsChannelConfigurationExists(ctx, testResourceTeamsChannelConfiguration, &teamschannelconfiguration), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfchatbot.ResourceTeamsChannelConfiguration, testResourceTeamsChannelConfiguration), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckTeamsChannelConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ChatbotClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_chatbot_teams_channel_configuration" { + continue + } + + _, err := tfchatbot.FindTeamsChannelConfigurationByTeamID(ctx, conn, rs.Primary.Attributes["team_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return create.Error(names.Chatbot, create.ErrActionCheckingDestroyed, tfchatbot.ResNameTeamsChannelConfiguration, rs.Primary.Attributes["team_id"], errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckTeamsChannelConfigurationExists(ctx context.Context, name string, teamschannelconfiguration *types.TeamsChannelConfiguration) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Chatbot, create.ErrActionCheckingExistence, tfchatbot.ResNameTeamsChannelConfiguration, name, errors.New("not found")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ChatbotClient(ctx) + + output, err := tfchatbot.FindTeamsChannelConfigurationByTeamID(ctx, conn, rs.Primary.Attributes["team_id"]) + + if err != nil { + return err + } + + *teamschannelconfiguration = *output + + return nil + } +} + +func testAccTeamsChannelConfigurationImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return rs.Primary.Attributes["team_id"], nil + } +} + +func testAccTeamsChannelConfigurationConfig_basic(rName, channelID, teamID, tenantID string) string { + return fmt.Sprintf(` +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["chatbot.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role" "test" { + name = %[1]q + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_chatbot_teams_channel_configuration" "test" { + channel_id = %[2]q + configuration_name = %[1]q + iam_role_arn = aws_iam_role.test.arn + team_id = %[3]q + tenant_id = %[4]q + + tags = { + Name = %[2]q + } +} +`, rName, channelID, teamID, tenantID) +} diff --git a/website/docs/r/chatbot_teams_channel_configuration.html.markdown b/website/docs/r/chatbot_teams_channel_configuration.html.markdown new file mode 100644 index 00000000000..d6f467059ae --- /dev/null +++ b/website/docs/r/chatbot_teams_channel_configuration.html.markdown @@ -0,0 +1,83 @@ +--- +subcategory: "Chatbot" +layout: "aws" +page_title: "AWS: aws_chatbot_teams_channel_configuration" +description: |- + Terraform resource for managing an AWS Chatbot Microsoft Teams Channel Configuration. +--- + +# Resource: aws_chatbot_teams_channel_configuration + +Terraform resource for managing an AWS Chatbot Microsoft Teams Channel Configuration. + +~> **NOTE:** We provide this resource on a best-effort basis. If you are able to test it and find it useful, we welcome your input at [GitHub](https://github.com/hashicorp/terraform-provider-aws). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_chatbot_teams_channel_configuration" "test" { + channel_id = "C07EZ1ABC23" + configuration_name = "mitt-lags-kanal" + iam_role_arn = aws_iam_role.test.arn + team_id = "74361522-da01-538d-aa2e-ac7918c6bb92" + tenant_id = "1234" + + tags = { + Name = "mitt-lags-kanal" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `channel_id` - (Required) ID of the Microsoft Teams channel. +* `configuration_name` - (Required) Name of the Microsoft Teams channel configuration. +* `iam_role_arn` - (Required) ARN of the IAM role that defines the permissions for AWS Chatbot. This is a user-defined role that AWS Chatbot will assume. This is not the service-linked role. +* `team_id` - (Required) ID of the Microsoft Team authorized with AWS Chatbot. To get the team ID, you must perform the initial authorization flow with Microsoft Teams in the AWS Chatbot console. Then you can copy and paste the team ID from the console. +* `tenant_id` - (Required) ID of the Microsoft Teams tenant. + +The following arguments are optional: + +* `channel_name` - (Optional) Name of the Microsoft Teams channel. +* `guardrail_policy_arns` - (Optional) List of IAM policy ARNs that are applied as channel guardrails. The AWS managed `AdministratorAccess` policy is applied by default if this is not set. +* `logging_level` - (Optional) Logging levels include `ERROR`, `INFO`, or `NONE`. +* `sns_topic_arns` - (Optional) ARNs of the SNS topics that deliver notifications to AWS Chatbot. +* `tags` - (Optional) Map of tags assigned to the resource. +* `team_name` - (Optional) Name of the Microsoft Teams team. +* `user_authorization_required` - (Optional) Enables use of a user role requirement in your chat configuration. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `chat_configuration_arn` - ARN of the Microsoft Teams channel configuration. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `20m`) +* `update` - (Default `20m`) +* `delete` - (Default `20m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Chatbot Microsoft Teams Channel Configuration using the `team_id`. For example: + +```terraform +import { + to = aws_chatbot_teams_channel_configuration.example + id = "5f4f15d2-b958-522a-8333-124aa8bf0925" +} +``` + +Using `terraform import`, import Chatbot Microsoft Teams Channel Configuration using the `team_id`. For example: + +```console +% terraform import aws_chatbot_teams_channel_configuration.example 5f4f15d2-b958-522a-8333-124aa8bf0925 +```