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

Add support for team notification configurations #1016

Merged
merged 11 commits into from
Jan 8, 2025
49 changes: 49 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,55 @@ func createNotificationConfiguration(t *testing.T, client *Client, w *Workspace,
}
}

func createTeamNotificationConfiguration(t *testing.T, client *Client, team *Team, options *NotificationConfigurationCreateOptions) (*NotificationConfiguration, func()) {
var tCleanup func()

if team == nil {
team, tCleanup = createTeam(t, client, nil)
}

// Team notification configurations do not actually require a run task, but we'll
// reuse this as a URL that returns a 200.
runTaskURL := os.Getenv("TFC_RUN_TASK_URL")
if runTaskURL == "" {
t.Error("You must set TFC_RUN_TASK_URL for run task related tests.")
}

if options == nil {
options = &NotificationConfigurationCreateOptions{
DestinationType: NotificationDestination(NotificationDestinationTypeGeneric),
Enabled: Bool(false),
Name: String(randomString(t)),
Token: String(randomString(t)),
URL: String(runTaskURL),
Triggers: []NotificationTriggerType{NotificationTriggerChangeRequestCreated},
SubscribableChoice: &NotificationConfigurationSubscribableChoice{Team: team},
}
}

ctx := context.Background()
nc, err := client.NotificationConfigurations.Create(
ctx,
team.ID,
*options,
)
if err != nil {
t.Fatal(err)
}

return nc, func() {
if err := client.NotificationConfigurations.Delete(ctx, nc.ID); err != nil {
t.Errorf("Error destroying team notification configuration! WARNING: Dangling\n"+
"resources may exist! The full error is shown below.\n\n"+
"NotificationConfiguration: %s\nError: %s", nc.ID, err)
}

if tCleanup != nil {
tCleanup()
}
}
}

func createPolicySetParameter(t *testing.T, client *Client, ps *PolicySet) (*PolicySetParameter, func()) {
var psCleanup func()

Expand Down
16 changes: 8 additions & 8 deletions mocks/notification_configuration_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 88 additions & 13 deletions notification_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ var _ NotificationConfigurations = (*notificationConfigurations)(nil)
// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/notification-configurations
type NotificationConfigurations interface {
// List all the notification configurations within a workspace.
List(ctx context.Context, workspaceID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error)
List(ctx context.Context, subscribableID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error)

// Create a new notification configuration with the given options.
Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error)
Create(ctx context.Context, subscribableID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error)

// Read a notification configuration by its ID.
Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)
Expand Down Expand Up @@ -59,6 +59,7 @@ const (
NotificationTriggerAssessmentCheckFailed NotificationTriggerType = "assessment:check_failure"
NotificationTriggerWorkspaceAutoDestroyReminder NotificationTriggerType = "workspace:auto_destroy_reminder"
NotificationTriggerWorkspaceAutoDestroyRunResults NotificationTriggerType = "workspace:auto_destroy_run_results"
NotificationTriggerChangeRequestCreated NotificationTriggerType = "change_request:created"
)

// NotificationDestinationType represents the destination type of the
Expand All @@ -80,6 +81,14 @@ type NotificationConfigurationList struct {
Items []*NotificationConfiguration
}

// NotificationConfigurationSubscribableChoice is a choice type struct that represents the possible values
// within a polymorphic relation. If a value is available, exactly one field
// will be non-nil.
type NotificationConfigurationSubscribableChoice struct {
Team *Team
Workspace *Workspace
}

// NotificationConfiguration represents a Notification Configuration.
type NotificationConfiguration struct {
ID string `jsonapi:"primary,notification-configurations"`
Expand All @@ -97,8 +106,11 @@ type NotificationConfiguration struct {
EmailAddresses []string `jsonapi:"attr,email-addresses"`

// Relations
Subscribable *Workspace `jsonapi:"relation,subscribable"`
EmailUsers []*User `jsonapi:"relation,users"`
// DEPRECATED. The subscribable field is polymorphic. Use NotificationConfigurationSubscribableChoice instead.
Subscribable *Workspace `jsonapi:"relation,subscribable,omitempty"`
SubscribableChoice *NotificationConfigurationSubscribableChoice `jsonapi:"polyrelation,subscribable"`

EmailUsers []*User `jsonapi:"relation,users"`
}

// DeliveryResponse represents a notification configuration delivery response.
Expand All @@ -115,6 +127,8 @@ type DeliveryResponse struct {
// notification configurations.
type NotificationConfigurationListOptions struct {
ListOptions

SubscribableChoice *NotificationConfigurationSubscribableChoice
}

// NotificationConfigurationCreateOptions represents the options for
Expand Down Expand Up @@ -150,6 +164,9 @@ type NotificationConfigurationCreateOptions struct {

// Optional: The list of users belonging to the organization that will receive notification emails.
EmailUsers []*User `jsonapi:"relation,users,omitempty"`

// Required: The workspace or team that the notification configuration is associated with.
SubscribableChoice *NotificationConfigurationSubscribableChoice `jsonapi:"polyrelation,subscribable"`
notchairmk marked this conversation as resolved.
Show resolved Hide resolved
}

// NotificationConfigurationUpdateOptions represents the options for
Expand Down Expand Up @@ -185,12 +202,32 @@ type NotificationConfigurationUpdateOptions struct {
}

// List all the notification configurations associated with a workspace.
func (s *notificationConfigurations) List(ctx context.Context, workspaceID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error) {
if !validStringID(&workspaceID) {
return nil, ErrInvalidWorkspaceID
func (s *notificationConfigurations) List(ctx context.Context, subscribableID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error) {
var u string
if options == nil {
options = &NotificationConfigurationListOptions{
SubscribableChoice: &NotificationConfigurationSubscribableChoice{
Workspace: &Workspace{ID: subscribableID},
},
}
} else if options.SubscribableChoice == nil {
options.SubscribableChoice = &NotificationConfigurationSubscribableChoice{
Workspace: &Workspace{ID: subscribableID},
}
}

if options.SubscribableChoice.Team != nil {
if !validStringID(&subscribableID) {
return nil, ErrInvalidTeamID
}
u = fmt.Sprintf("teams/%s/notification-configurations", url.PathEscape(subscribableID))
} else {
if !validStringID(&subscribableID) {
return nil, ErrInvalidWorkspaceID
}
u = fmt.Sprintf("workspaces/%s/notification-configurations", url.PathEscape(subscribableID))
}

u := fmt.Sprintf("workspaces/%s/notification-configurations", url.PathEscape(workspaceID))
req, err := s.client.NewRequest("GET", u, options)
if err != nil {
return nil, err
Expand All @@ -202,30 +239,43 @@ func (s *notificationConfigurations) List(ctx context.Context, workspaceID strin
return nil, err
}

for i := range ncl.Items {
backfillDeprecatedSubscribable(ncl.Items[i])
}

return ncl, nil
}

// Create a notification configuration with the given options.
func (s *notificationConfigurations) Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) {
if !validStringID(&workspaceID) {
return nil, ErrInvalidWorkspaceID
func (s *notificationConfigurations) Create(ctx context.Context, subscribableID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) {
var u string
var subscribableChoice *NotificationConfigurationSubscribableChoice
if options.SubscribableChoice == nil || options.SubscribableChoice.Team == nil {
u = fmt.Sprintf("workspaces/%s/notification-configurations", url.PathEscape(subscribableID))
options.SubscribableChoice = &NotificationConfigurationSubscribableChoice{Workspace: &Workspace{ID: subscribableID}}
} else {
u = fmt.Sprintf("teams/%s/notification-configurations", url.PathEscape(subscribableID))
options.SubscribableChoice = &NotificationConfigurationSubscribableChoice{Team: &Team{ID: subscribableID}}
}

if err := options.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("workspaces/%s/notification-configurations", url.PathEscape(workspaceID))
req, err := s.client.NewRequest("POST", u, &options)
if err != nil {
return nil, err
}

nc := &NotificationConfiguration{}
nc := &NotificationConfiguration{SubscribableChoice: subscribableChoice}
err = req.Do(ctx, nc)

if err != nil {
return nil, err
}

backfillDeprecatedSubscribable(nc)

return nc, nil
}

Expand All @@ -247,6 +297,8 @@ func (s *notificationConfigurations) Read(ctx context.Context, notificationConfi
return nil, err
}

backfillDeprecatedSubscribable(nc)

return nc, nil
}

Expand All @@ -272,6 +324,8 @@ func (s *notificationConfigurations) Update(ctx context.Context, notificationCon
return nil, err
}

backfillDeprecatedSubscribable(nc)

return nc, nil
}

Expand Down Expand Up @@ -314,6 +368,16 @@ func (s *notificationConfigurations) Verify(ctx context.Context, notificationCon
}

func (o NotificationConfigurationCreateOptions) valid() error {
if o.SubscribableChoice == nil || o.SubscribableChoice.Workspace != nil {
if !validStringID(&o.SubscribableChoice.Workspace.ID) {
return ErrInvalidWorkspaceID
}
} else {
if !validStringID(&o.SubscribableChoice.Team.ID) {
return ErrInvalidTeamID
}
}

if o.DestinationType == nil {
return ErrRequiredDestinationType
}
Expand Down Expand Up @@ -350,6 +414,16 @@ func (o NotificationConfigurationUpdateOptions) valid() error {
return nil
}

func backfillDeprecatedSubscribable(notification *NotificationConfiguration) {
if notification.Subscribable != nil || notification.SubscribableChoice == nil {
return
}

if notification.SubscribableChoice.Workspace != nil {
notification.Subscribable = notification.SubscribableChoice.Workspace
}
}

func validNotificationTriggerType(triggers []NotificationTriggerType) bool {
for _, t := range triggers {
switch t {
Expand All @@ -363,6 +437,7 @@ func validNotificationTriggerType(triggers []NotificationTriggerType) bool {
NotificationTriggerAssessmentFailed,
NotificationTriggerWorkspaceAutoDestroyReminder,
NotificationTriggerWorkspaceAutoDestroyRunResults,
NotificationTriggerChangeRequestCreated,
NotificationTriggerAssessmentCheckFailed:
continue
default:
Expand Down
Loading
Loading