1package tfe
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"net/http"
8	"net/url"
9	"time"
10)
11
12// Compile-time proof of interface implementation.
13var _ NotificationConfigurations = (*notificationConfigurations)(nil)
14
15// NotificationConfigurations describes all the Notification Configuration
16// related methods that the Terraform Enterprise API supports.
17//
18// TFE API docs:
19// https://www.terraform.io/docs/enterprise/api/notification-configurations.html
20type NotificationConfigurations interface {
21	// List all the notification configurations within a workspace.
22	List(ctx context.Context, workspaceID string, options NotificationConfigurationListOptions) (*NotificationConfigurationList, error)
23
24	// Create a new notification configuration with the given options.
25	Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error)
26
27	// Read a notification configuration by its ID.
28	Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)
29
30	// Update an existing notification configuration.
31	Update(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error)
32
33	// Delete a notification configuration by its ID.
34	Delete(ctx context.Context, notificationConfigurationID string) error
35
36	// Verify a notification configuration by its ID.
37	Verify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error)
38}
39
40// notificationConfigurations implements NotificationConfigurations.
41type notificationConfigurations struct {
42	client *Client
43}
44
45// List of available notification triggers.
46const (
47	NotificationTriggerCreated        string = "run:created"
48	NotificationTriggerPlanning       string = "run:planning"
49	NotificationTriggerNeedsAttention string = "run:needs_attention"
50	NotificationTriggerApplying       string = "run:applying"
51	NotificationTriggerCompleted      string = "run:completed"
52	NotificationTriggerErrored        string = "run:errored"
53)
54
55// NotificationDestinationType represents the destination type of the
56// notification configuration.
57type NotificationDestinationType string
58
59// List of available notification destination types.
60const (
61	NotificationDestinationTypeEmail   NotificationDestinationType = "email"
62	NotificationDestinationTypeGeneric NotificationDestinationType = "generic"
63	NotificationDestinationTypeSlack   NotificationDestinationType = "slack"
64)
65
66// NotificationConfigurationList represents a list of Notification
67// Configurations.
68type NotificationConfigurationList struct {
69	*Pagination
70	Items []*NotificationConfiguration
71}
72
73// NotificationConfiguration represents a Notification Configuration.
74type NotificationConfiguration struct {
75	ID                string                      `jsonapi:"primary,notification-configurations"`
76	CreatedAt         time.Time                   `jsonapi:"attr,created-at,iso8601"`
77	DeliveryResponses []*DeliveryResponse         `jsonapi:"attr,delivery-responses"`
78	DestinationType   NotificationDestinationType `jsonapi:"attr,destination-type"`
79	Enabled           bool                        `jsonapi:"attr,enabled"`
80	Name              string                      `jsonapi:"attr,name"`
81	Token             string                      `jsonapi:"attr,token"`
82	Triggers          []string                    `jsonapi:"attr,triggers"`
83	UpdatedAt         time.Time                   `jsonapi:"attr,updated-at,iso8601"`
84	URL               string                      `jsonapi:"attr,url"`
85
86	// EmailAddresses is only available for TFE users. It is not available in TFC.
87	EmailAddresses []string `jsonapi:"attr,email-addresses"`
88
89	// Relations
90	Subscribable *Workspace `jsonapi:"relation,subscribable"`
91	EmailUsers   []*User    `jsonapi:"relation,users"`
92}
93
94// DeliveryResponse represents a notification configuration delivery response.
95type DeliveryResponse struct {
96	Body       string      `json:"body"`
97	Code       int         `json:"code"`
98	Headers    http.Header `json:"headers"`
99	SentAt     time.Time   `json:"sent-at,iso8601"`
100	Successful bool        `json:"successful"`
101	URL        string      `json:"url"`
102}
103
104// NotificationConfigurationListOptions represents the options for listing
105// notification configurations.
106type NotificationConfigurationListOptions struct {
107	ListOptions
108}
109
110// List all the notification configurations associated with a workspace.
111func (s *notificationConfigurations) List(ctx context.Context, workspaceID string, options NotificationConfigurationListOptions) (*NotificationConfigurationList, error) {
112	if !validStringID(&workspaceID) {
113		return nil, errors.New("invalid value for workspace ID")
114	}
115
116	u := fmt.Sprintf("workspaces/%s/notification-configurations", url.QueryEscape(workspaceID))
117	req, err := s.client.newRequest("GET", u, options)
118	if err != nil {
119		return nil, err
120	}
121
122	ncl := &NotificationConfigurationList{}
123	err = s.client.do(ctx, req, ncl)
124	if err != nil {
125		return nil, err
126	}
127
128	return ncl, nil
129}
130
131// NotificationConfigurationCreateOptions represents the options for
132// creating a new notification configuration.
133type NotificationConfigurationCreateOptions struct {
134	// For internal use only!
135	ID string `jsonapi:"primary,notification-configurations"`
136
137	// The destination type of the notification configuration
138	DestinationType *NotificationDestinationType `jsonapi:"attr,destination-type"`
139
140	// Whether the notification configuration should be enabled or not
141	Enabled *bool `jsonapi:"attr,enabled"`
142
143	// The name of the notification configuration
144	Name *string `jsonapi:"attr,name"`
145
146	// The token of the notification configuration
147	Token *string `jsonapi:"attr,token,omitempty"`
148
149	// The list of run events that will trigger notifications.
150	Triggers []string `jsonapi:"attr,triggers,omitempty"`
151
152	// The url of the notification configuration
153	URL *string `jsonapi:"attr,url,omitempty"`
154
155	// The list of email addresses that will receive notification emails.
156	// EmailAddresses is only available for TFE users. It is not available in TFC.
157	EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"`
158
159	// The list of users belonging to the organization that will receive notification emails.
160	EmailUsers []*User `jsonapi:"relation,users,omitempty"`
161}
162
163func (o NotificationConfigurationCreateOptions) valid() error {
164	if o.DestinationType == nil {
165		return errors.New("destination type is required")
166	}
167	if o.Enabled == nil {
168		return errors.New("enabled is required")
169	}
170	if !validString(o.Name) {
171		return errors.New("name is required")
172	}
173
174	if *o.DestinationType == NotificationDestinationTypeGeneric || *o.DestinationType == NotificationDestinationTypeSlack {
175		if o.URL == nil {
176			return errors.New("url is required")
177		}
178	}
179	return nil
180}
181
182// Creates a notification configuration with the given options.
183func (s *notificationConfigurations) Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) {
184	if !validStringID(&workspaceID) {
185		return nil, errors.New("invalid value for workspace ID")
186	}
187	if err := options.valid(); err != nil {
188		return nil, err
189	}
190
191	// Make sure we don't send a user provided ID.
192	options.ID = ""
193
194	u := fmt.Sprintf("workspaces/%s/notification-configurations", url.QueryEscape(workspaceID))
195	req, err := s.client.newRequest("POST", u, &options)
196	if err != nil {
197		return nil, err
198	}
199
200	nc := &NotificationConfiguration{}
201	err = s.client.do(ctx, req, nc)
202	if err != nil {
203		return nil, err
204	}
205
206	return nc, nil
207}
208
209// Read a notification configuration by its ID.
210func (s *notificationConfigurations) Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) {
211	if !validStringID(&notificationConfigurationID) {
212		return nil, errors.New("invalid value for notification configuration ID")
213	}
214
215	u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID))
216	req, err := s.client.newRequest("GET", u, nil)
217	if err != nil {
218		return nil, err
219	}
220
221	nc := &NotificationConfiguration{}
222	err = s.client.do(ctx, req, nc)
223	if err != nil {
224		return nil, err
225	}
226
227	return nc, nil
228}
229
230// NotificationConfigurationUpdateOptions represents the options for
231// updating a existing notification configuration.
232type NotificationConfigurationUpdateOptions struct {
233	// For internal use only!
234	ID string `jsonapi:"primary,notification-configurations"`
235
236	// Whether the notification configuration should be enabled or not
237	Enabled *bool `jsonapi:"attr,enabled,omitempty"`
238
239	// The name of the notification configuration
240	Name *string `jsonapi:"attr,name,omitempty"`
241
242	// The token of the notification configuration
243	Token *string `jsonapi:"attr,token,omitempty"`
244
245	// The list of run events that will trigger notifications.
246	Triggers []string `jsonapi:"attr,triggers,omitempty"`
247
248	// The url of the notification configuration
249	URL *string `jsonapi:"attr,url,omitempty"`
250
251	// The list of email addresses that will receive notification emails.
252	// EmailAddresses is only available for TFE users. It is not available in TFC.
253	EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"`
254
255	// The list of users belonging to the organization that will receive notification emails.
256	EmailUsers []*User `jsonapi:"relation,users,omitempty"`
257}
258
259// Updates a notification configuration with the given options.
260func (s *notificationConfigurations) Update(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error) {
261	if !validStringID(&notificationConfigurationID) {
262		return nil, errors.New("invalid value for notification configuration ID")
263	}
264
265	// Make sure we don't send a user provided ID.
266	options.ID = ""
267
268	u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID))
269	req, err := s.client.newRequest("PATCH", u, &options)
270	if err != nil {
271		return nil, err
272	}
273
274	nc := &NotificationConfiguration{}
275	err = s.client.do(ctx, req, nc)
276	if err != nil {
277		return nil, err
278	}
279
280	return nc, nil
281}
282
283// Delete a notifications configuration by its ID.
284func (s *notificationConfigurations) Delete(ctx context.Context, notificationConfigurationID string) error {
285	if !validStringID(&notificationConfigurationID) {
286		return errors.New("invalid value for notification configuration ID")
287	}
288
289	u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID))
290	req, err := s.client.newRequest("DELETE", u, nil)
291	if err != nil {
292		return err
293	}
294
295	return s.client.do(ctx, req, nil)
296}
297
298// Verifies a notification configuration by delivering a verification
299// payload to the configured url.
300func (s *notificationConfigurations) Verify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) {
301	if !validStringID(&notificationConfigurationID) {
302		return nil, errors.New("invalid value for notification configuration ID")
303	}
304
305	u := fmt.Sprintf(
306		"notification-configurations/%s/actions/verify", url.QueryEscape(notificationConfigurationID))
307	req, err := s.client.newRequest("POST", u, nil)
308	if err != nil {
309		return nil, err
310	}
311
312	nc := &NotificationConfiguration{}
313	err = s.client.do(ctx, req, nc)
314	if err != nil {
315		return nil, err
316	}
317
318	return nc, nil
319}
320