diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2edef64..dfeb9daa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ FEATURES: * **New Resource**: `tfe_workspace_run_task` ([#488](https://github.com/hashicorp/terraform-provider-tfe/pull/488)) * **New Data Source**: d/tfe_organization_run_task ([#488](https://github.com/hashicorp/terraform-provider-tfe/pull/488)) * **New Data Source**: d/tfe_workspace_run_task ([#488](https://github.com/hashicorp/terraform-provider-tfe/pull/488)) +* r/tfe_notification_configuration: Add Microsoft Teams notification type ([#484](https://github.com/hashicorp/terraform-provider-tfe/pull/484)) BREAKING CHANGES: * r/tfe_workspace: Default value of the `file_triggers_enabled` field is changed to `false`. This will align the diff --git a/go.mod b/go.mod index 373b0709f..83c70540e 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/go-slug v0.8.1 - github.com/hashicorp/go-tfe v1.2.0 + github.com/hashicorp/go-tfe v1.3.0 github.com/hashicorp/go-version v1.5.0 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce github.com/hashicorp/hcl/v2 v2.10.0 // indirect @@ -51,7 +51,7 @@ require ( github.com/hashicorp/go-getter v1.5.3 // indirect github.com/hashicorp/go-plugin v1.4.1 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.15.0 // indirect diff --git a/go.sum b/go.sum index 355a151b5..6bd94f5b8 100644 --- a/go.sum +++ b/go.sum @@ -219,10 +219,16 @@ github.com/hashicorp/go-slug v0.8.1 h1:srN7ivgAjHfZddYY1DjBaihRCFy20+vCcOrlx1O2A github.com/hashicorp/go-slug v0.8.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= github.com/hashicorp/go-tfe v1.2.0 h1:L29LCo/qIjOqBUjfiUsZSAzBdxmsOLzwnwZpA+68WW8= github.com/hashicorp/go-tfe v1.2.0/go.mod h1:tJF/OlAXzVbmjiimAPLplSLgwg6kZDUOy0MzHuMwvF4= +github.com/hashicorp/go-tfe v1.2.1-0.20220607193402-515f37b540ef h1:asJwJP0YiJsUWGsHHHiFnWeWiXiWU5WSpbS9mEPOOCE= +github.com/hashicorp/go-tfe v1.2.1-0.20220607193402-515f37b540ef/go.mod h1:a8UuFhW0D+aLZkFLFxwu8wr/DmPma1544mx/nsOTy/U= +github.com/hashicorp/go-tfe v1.3.0 h1:5sboIfj0Uz6YAfPeDAVRXBKf3EI3D054kTbmOoUUW3g= +github.com/hashicorp/go-tfe v1.3.0/go.mod h1:5PORBlPPMya01sElYhCLUMu07BHGTwP5CRedU26SjPM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= diff --git a/tfe/resource_tfe_notification_configuration.go b/tfe/resource_tfe_notification_configuration.go index ecd092ac0..124958322 100644 --- a/tfe/resource_tfe_notification_configuration.go +++ b/tfe/resource_tfe_notification_configuration.go @@ -34,6 +34,7 @@ func resourceTFENotificationConfiguration() *schema.Resource { string(tfe.NotificationDestinationTypeEmail), string(tfe.NotificationDestinationTypeGeneric), string(tfe.NotificationDestinationTypeSlack), + string(tfe.NotificationDestinationTypeMicrosoftTeams), }, false, ), @@ -138,6 +139,14 @@ func resourceTFENotificationConfigurationCreate(d *schema.ResourceData, meta int if err != nil { return err } + } else if destinationType == tfe.NotificationDestinationTypeMicrosoftTeams { + // When destination_type is 'microsoft-teams': + // 1. email_addresses, email_user_ids, and token cannot be set + // 2. url must be set + err := validateSchemaAttributesForDestinationTypeMicrosoftTeams(d) + if err != nil { + return err + } } // Create a new options struct @@ -259,6 +268,14 @@ func resourceTFENotificationConfigurationUpdate(d *schema.ResourceData, meta int if err != nil { return err } + } else if destinationType == tfe.NotificationDestinationTypeMicrosoftTeams { + // When destination_type is 'microsoft-teams': + // 1. email_addresses, email_user_ids, and token cannot be set + // 2. url must be set + err := validateSchemaAttributesForDestinationTypeMicrosoftTeams(d) + if err != nil { + return err + } } // Create a new options struct @@ -370,3 +387,27 @@ func validateSchemaAttributesForDestinationTypeSlack(d *schema.ResourceData) err return nil } + +func validateSchemaAttributesForDestinationTypeMicrosoftTeams(d *schema.ResourceData) error { + // Make sure email_addresses, email_user_ids, and token are not set when destination_type is 'microsoft-teams' + _, emailAddressesIsSet := d.GetOk("email_addresses") + if emailAddressesIsSet { + return fmt.Errorf("Email addresses cannot be set with destination type of %s", string(tfe.NotificationDestinationTypeMicrosoftTeams)) + } + _, emailUserIDsIsSet := d.GetOk("email_user_ids") + if emailUserIDsIsSet { + return fmt.Errorf("Email user IDs cannot be set with destination type of %s", string(tfe.NotificationDestinationTypeMicrosoftTeams)) + } + token, tokenIsSet := d.GetOk("token") + if tokenIsSet && token != "" { + return fmt.Errorf("Token cannot be set with destination type of %s", string(tfe.NotificationDestinationTypeMicrosoftTeams)) + } + + // Make sure url is set when destination_type is 'microsoft-teams' + _, urlIsSet := d.GetOk("url") + if !urlIsSet { + return fmt.Errorf("URL is required with destination type of %s", string(tfe.NotificationDestinationTypeMicrosoftTeams)) + } + + return nil +} diff --git a/tfe/resource_tfe_notification_configuration_test.go b/tfe/resource_tfe_notification_configuration_test.go index 34fe75eb8..0d9b27cb5 100644 --- a/tfe/resource_tfe_notification_configuration_test.go +++ b/tfe/resource_tfe_notification_configuration_test.go @@ -248,6 +248,33 @@ func TestAccTFENotificationConfiguration_validateSchemaAttributesSlack(t *testin }) } +func TestAccTFENotificationConfiguration_validateSchemaAttributesMicrosoftTeams(t *testing.T) { + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithEmailAddresses(rInt), + ExpectError: regexp.MustCompile(`Email addresses cannot be set with destination type of microsoft-teams`), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithEmailUserIDs(rInt), + ExpectError: regexp.MustCompile(`Email user IDs cannot be set with destination type of microsoft-teams`), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithToken(rInt), + ExpectError: regexp.MustCompile(`Token cannot be set with destination type of microsoft-teams`), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithoutURL(rInt), + ExpectError: regexp.MustCompile(`URL is required with destination type of microsoft-teams`), + }, + }, + }) +} + func TestAccTFENotificationConfiguration_updateValidateSchemaAttributesEmail(t *testing.T) { notificationConfiguration := &tfe.NotificationConfiguration{} rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() @@ -377,6 +404,49 @@ func TestAccTFENotificationConfiguration_updateValidateSchemaAttributesSlack(t * }) } +func TestAccTFENotificationConfiguration_updateValidateSchemaAttributesMicrosoftTeams(t *testing.T) { + notificationConfiguration := &tfe.NotificationConfiguration{} + rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckTFENotificationConfigurationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTFENotificationConfiguration_microsoftTeams(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckTFENotificationConfigurationExists( + "tfe_notification_configuration.foobar", notificationConfiguration), + testAccCheckTFENotificationConfigurationAttributesMicrosoftTeams(notificationConfiguration), + resource.TestCheckResourceAttr( + "tfe_notification_configuration.foobar", "destination_type", "microsoft-teams"), + resource.TestCheckResourceAttr( + "tfe_notification_configuration.foobar", "name", "notification_msteams"), + resource.TestCheckResourceAttr( + "tfe_notification_configuration.foobar", "url", "http://example.com"), + ), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithEmailAddresses(rInt), + ExpectError: regexp.MustCompile(`Email addresses cannot be set with destination type of microsoft-teams`), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithEmailUserIDs(rInt), + ExpectError: regexp.MustCompile(`Email user IDs cannot be set with destination type of microsoft-teams`), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithToken(rInt), + ExpectError: regexp.MustCompile(`Token cannot be set with destination type of microsoft-teams`), + }, + { + Config: testAccTFENotificationConfiguration_microsoftTeamsWithoutURL(rInt), + ExpectError: regexp.MustCompile(`URL is required with destination type of microsoft-teams`), + }, + }, + }) +} + func TestAccTFENotificationConfiguration_duplicateTriggers(t *testing.T) { notificationConfiguration := &tfe.NotificationConfiguration{} rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int() @@ -638,6 +708,32 @@ func testAccCheckTFENotificationConfigurationAttributesSlack(notificationConfigu } } +func testAccCheckTFENotificationConfigurationAttributesMicrosoftTeams(notificationConfiguration *tfe.NotificationConfiguration) resource.TestCheckFunc { + return func(s *terraform.State) error { + if notificationConfiguration.Name != "notification_msteams" { + return fmt.Errorf("Bad name: %s", notificationConfiguration.Name) + } + + if notificationConfiguration.DestinationType != tfe.NotificationDestinationTypeMicrosoftTeams { + return fmt.Errorf("Bad destination type: %s", notificationConfiguration.DestinationType) + } + + if notificationConfiguration.Enabled != false { + return fmt.Errorf("Bad enabled value: %t", notificationConfiguration.Enabled) + } + + if !reflect.DeepEqual(notificationConfiguration.Triggers, []string{}) { + return fmt.Errorf("Bad triggers: %v", notificationConfiguration.Triggers) + } + + if notificationConfiguration.URL != "http://example.com" { + return fmt.Errorf("Bad URL: %s", notificationConfiguration.URL) + } + + return nil + } +} + func testAccCheckTFENotificationConfigurationAttributesDuplicateTriggers(notificationConfiguration *tfe.NotificationConfiguration) resource.TestCheckFunc { return func(s *terraform.State) error { if notificationConfiguration.Name != "notification_duplicate_triggers" { @@ -751,6 +847,26 @@ resource "tfe_notification_configuration" "foobar" { }`, rInt) } +func testAccTFENotificationConfiguration_microsoftTeams(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_notification_configuration" "foobar" { + name = "notification_msteams" + destination_type = "microsoft-teams" + url = "http://example.com" + workspace_id = tfe_workspace.foobar.id +}`, rInt) +} + func testAccTFENotificationConfiguration_update(rInt int) string { return fmt.Sprintf(` resource "tfe_organization" "foobar" { @@ -989,6 +1105,91 @@ resource "tfe_notification_configuration" "foobar" { }`, rInt) } +func testAccTFENotificationConfiguration_microsoftTeamsWithEmailAddresses(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_notification_configuration" "foobar" { + name = "notification_msteams_with_email_addresses" + destination_type = "microsoft-teams" + email_addresses = ["test@example.com", "test2@example.com"] + workspace_id = tfe_workspace.foobar.id +}`, rInt) +} + +func testAccTFENotificationConfiguration_microsoftTeamsWithEmailUserIDs(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_organization_membership" "foobar" { + organization = tfe_organization.foobar.id + email = "foo@foobar.com" +} + +resource "tfe_notification_configuration" "foobar" { + name = "notification_msteams_with_email_user_ids" + destination_type = "microsoft-teams" + email_user_ids = [tfe_organization_membership.foobar.id] + workspace_id = tfe_workspace.foobar.id +}`, rInt) +} + +func testAccTFENotificationConfiguration_microsoftTeamsWithToken(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_notification_configuration" "foobar" { + name = "notification_msteams_with_token" + destination_type = "microsoft-teams" + token = "1234567890" + url = "http://example.com" + workspace_id = tfe_workspace.foobar.id +}`, rInt) +} + +func testAccTFENotificationConfiguration_microsoftTeamsWithoutURL(rInt int) string { + return fmt.Sprintf(` +resource "tfe_organization" "foobar" { + name = "tst-terraform-%d" + email = "admin@company.com" +} + +resource "tfe_workspace" "foobar" { + name = "workspace-test" + organization = tfe_organization.foobar.id +} + +resource "tfe_notification_configuration" "foobar" { + name = "notification_msteams_without_url" + destination_type = "microsoft-teams" + workspace_id = tfe_workspace.foobar.id +}`, rInt) +} + func testAccTFENotificationConfiguration_duplicateTriggers(rInt int) string { return fmt.Sprintf(` resource "tfe_organization" "foobar" { diff --git a/tfe/resource_tfe_team_test.go b/tfe/resource_tfe_team_test.go index 56c90624c..cc36bd216 100644 --- a/tfe/resource_tfe_team_test.go +++ b/tfe/resource_tfe_team_test.go @@ -251,8 +251,8 @@ func testAccCheckTFETeamAttributes_full( if !team.OrganizationAccess.ManageRunTasks { return fmt.Errorf("OrganizationAccess.ManageRunTasks should be true") } - if *team.SSOTeamID != "team-test-sso-id" { - return fmt.Errorf("Bad SSO Team ID: %s", *team.SSOTeamID) + if team.SSOTeamID != "team-test-sso-id" { + return fmt.Errorf("Bad SSO Team ID: %s", team.SSOTeamID) } return nil @@ -283,8 +283,8 @@ func testAccCheckTFETeamAttributes_full_update( return fmt.Errorf("OrganizationAccess.ManageRunTasks should be false") } - if *team.SSOTeamID != "changed-sso-id" { - return fmt.Errorf("Bad SSO Team ID: %s", *team.SSOTeamID) + if team.SSOTeamID != "changed-sso-id" { + return fmt.Errorf("Bad SSO Team ID: %s", team.SSOTeamID) } return nil diff --git a/website/docs/r/notification_configuration.html.markdown b/website/docs/r/notification_configuration.html.markdown index 87aeadc50..21aae7c56 100644 --- a/website/docs/r/notification_configuration.html.markdown +++ b/website/docs/r/notification_configuration.html.markdown @@ -12,9 +12,6 @@ Terraform Cloud can be configured to send notifications for run state transition Notification configurations allow you to specify a URL, destination type, and what events will trigger the notification. Each workspace can have up to 20 notification configurations, and they apply to all runs for that workspace. -~> **NOTE:** Using `destination_type` of `email` requires using the provider with Terraform Cloud or an instance of - Terraform Enterprise at least as recent as v202005-1. - ## Example Usage @@ -104,21 +101,25 @@ The following arguments are supported: * `name` - (Required) Name of the notification configuration. * `destination_type` - (Required) The type of notification configuration payload to send. - Valid values are `email`, `generic` or `slack`. + Valid values are: + * `generic` + * `email` available in Terraform Cloud or Terraform Enterprise v202005-1 or later + * `slack` + * `microsoft-teams` available in Terraform Cloud or Terraform Enterprise v202206-1 or later * `email_addresses` - (Optional) **TFE only** A list of email addresses. This value - _must not_ be provided if `destination_type` is `generic` or `slack`. + _must not_ be provided if `destination_type` is `generic`, `microsoft-teams`, or `slack`. * `email_user_ids` - (Optional) A list of user IDs. This value _must not_ be provided - if `destination_type` is `generic` or `slack`. + if `destination_type` is `generic`, `microsoft-teams`, or `slack`. * `enabled` - (Optional) Whether the notification configuration should be enabled or not. Disabled configurations will not send any notifications. Defaults to `false`. * `token` - (Optional) A write-only secure token for the notification configuration, which can be used by the receiving server to verify request authenticity when configured for notification configurations with a destination type of `generic`. Defaults to `null`. - This value _must not_ be provided if `destination_type` is `email` or `slack`. + This value _must not_ be provided if `destination_type` is `email`, `microsoft-teams`, or `slack`. * `triggers` - (Optional) The array of triggers for which this notification configuration will send notifications. Valid values are `run:created`, `run:planning`, `run:needs_attention`, `run:applying` `run:completed`, `run:errored`. If omitted, no notification triggers are configured. -* `url` - (Required if `destination_type` is `generic` or `slack`) The HTTP or HTTPS URL of the notification +* `url` - (Required if `destination_type` is `generic`, `microsoft-teams`, or `slack`) The HTTP or HTTPS URL of the notification configuration where notification requests will be made. This value _must not_ be provided if `destination_type` is `email`. * `workspace_id` - (Required) The id of the workspace that owns the notification configuration.