Skip to content

Commit 8b4b886

Browse files
deanhuynhmarcelopv
andauthored
Destination subscriptions resource (#62)
Co-authored-by: Marcelo Vargas <[email protected]>
1 parent 406ca7b commit 8b4b886

File tree

6 files changed

+614
-0
lines changed

6 files changed

+614
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "segment_destination_subscription Resource - terraform-provider-segment"
4+
subcategory: ""
5+
description: |-
6+
7+
---
8+
9+
# segment_destination_subscription (Resource)
10+
11+
12+
13+
## Example Usage
14+
15+
```terraform
16+
# Configures a specific destination subscription
17+
resource "segment_destination_subscription" "send_to_webhook" {
18+
destination_id = segment_destination.webhook.id
19+
name = "Webhook send subscription"
20+
enabled = true
21+
action_id = "abc123"
22+
trigger = "type = \"track\""
23+
settings = jsonencode({
24+
"url" : "https://webhook.site/abc-123",
25+
"data" : {
26+
"@path" : "$.context.app"
27+
},
28+
"enable_batching" : false,
29+
"method" : "POST"
30+
})
31+
}
32+
```
33+
34+
<!-- schema generated by tfplugindocs -->
35+
## Schema
36+
37+
### Required
38+
39+
- `action_id` (String) The unique identifier for the Destination action to trigger.
40+
- `destination_id` (String) The associated Destination instance id.
41+
- `enabled` (Boolean) Is the subscription enabled.
42+
- `name` (String) The name of the subscription.
43+
- `settings` (String) The customer settings for action fields.
44+
- `trigger` (String) FQL string that describes what events should trigger a Destination action.
45+
46+
### Optional
47+
48+
- `model_id` (String) The unique identifier for the linked ReverseETLModel, if this part of a Reverse ETL connection.
49+
50+
### Read-Only
51+
52+
- `action_slug` (String) The URL-friendly key for the associated Destination action.
53+
- `id` (String) The unique identifier for the subscription.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Configures a specific destination subscription
2+
resource "segment_destination_subscription" "send_to_webhook" {
3+
destination_id = segment_destination.webhook.id
4+
name = "Webhook send subscription"
5+
enabled = true
6+
action_id = "abc123"
7+
trigger = "type = \"track\""
8+
settings = jsonencode({
9+
"url" : "https://webhook.site/abc-123",
10+
"data" : {
11+
"@path" : "$.context.app"
12+
},
13+
"enable_batching" : false,
14+
"method" : "POST"
15+
})
16+
}
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/segmentio/terraform-provider-segment/internal/provider/models"
9+
10+
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
11+
"github.com/hashicorp/terraform-plugin-framework/path"
12+
"github.com/hashicorp/terraform-plugin-framework/resource"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
15+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
16+
17+
"github.com/segmentio/public-api-sdk-go/api"
18+
)
19+
20+
var (
21+
_ resource.Resource = &destinationSubscriptionResource{}
22+
_ resource.ResourceWithConfigure = &destinationSubscriptionResource{}
23+
_ resource.ResourceWithImportState = &destinationSubscriptionResource{}
24+
)
25+
26+
func NewDestinationSubscriptionResource() resource.Resource {
27+
return &destinationSubscriptionResource{}
28+
}
29+
30+
type destinationSubscriptionResource struct {
31+
client *api.APIClient
32+
authContext context.Context
33+
}
34+
35+
func (r *destinationSubscriptionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
36+
resp.TypeName = req.ProviderTypeName + "_destination_subscription"
37+
}
38+
39+
func (r *destinationSubscriptionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
40+
resp.Schema = schema.Schema{
41+
Attributes: map[string]schema.Attribute{
42+
"id": schema.StringAttribute{
43+
Computed: true,
44+
Description: "The unique identifier for the subscription.",
45+
PlanModifiers: []planmodifier.String{
46+
stringplanmodifier.UseStateForUnknown(),
47+
},
48+
},
49+
"destination_id": schema.StringAttribute{
50+
Required: true,
51+
Description: "The associated Destination instance id.",
52+
PlanModifiers: []planmodifier.String{
53+
stringplanmodifier.RequiresReplace(),
54+
},
55+
},
56+
"name": schema.StringAttribute{
57+
Required: true,
58+
Description: "The name of the subscription.",
59+
},
60+
"enabled": schema.BoolAttribute{
61+
Required: true,
62+
Description: "Is the subscription enabled.",
63+
},
64+
"action_id": schema.StringAttribute{
65+
Required: true,
66+
Description: "The unique identifier for the Destination action to trigger.",
67+
PlanModifiers: []planmodifier.String{
68+
stringplanmodifier.RequiresReplace(),
69+
},
70+
},
71+
"action_slug": schema.StringAttribute{
72+
Computed: true,
73+
Description: "The URL-friendly key for the associated Destination action.",
74+
},
75+
"trigger": schema.StringAttribute{
76+
Required: true,
77+
Description: "FQL string that describes what events should trigger a Destination action.",
78+
},
79+
"model_id": schema.StringAttribute{
80+
Optional: true,
81+
Description: "The unique identifier for the linked ReverseETLModel, if this part of a Reverse ETL connection.",
82+
PlanModifiers: []planmodifier.String{
83+
stringplanmodifier.RequiresReplace(),
84+
},
85+
},
86+
"settings": schema.StringAttribute{
87+
Required: true,
88+
Description: `The customer settings for action fields.`,
89+
CustomType: jsontypes.NormalizedType{},
90+
},
91+
},
92+
}
93+
}
94+
95+
func (r *destinationSubscriptionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
96+
var plan models.DestinationSubscriptionState
97+
diags := req.Plan.Get(ctx, &plan)
98+
resp.Diagnostics.Append(diags...)
99+
if resp.Diagnostics.HasError() {
100+
return
101+
}
102+
103+
var settings map[string]interface{}
104+
diags = plan.Settings.Unmarshal(&settings)
105+
resp.Diagnostics.Append(diags...)
106+
if resp.Diagnostics.HasError() {
107+
return
108+
}
109+
modelMap := api.NewModelMap(settings)
110+
111+
out, body, err := r.client.DestinationsApi.CreateDestinationSubscription(r.authContext, plan.DestinationID.ValueString()).CreateDestinationSubscriptionAlphaInput(api.CreateDestinationSubscriptionAlphaInput{
112+
Name: plan.Name.ValueString(),
113+
ActionId: plan.ActionID.ValueString(),
114+
Trigger: plan.Trigger.ValueString(),
115+
Enabled: plan.Enabled.ValueBool(),
116+
ModelId: plan.ModelID.ValueStringPointer(),
117+
Settings: *api.NewNullableModelMap(modelMap),
118+
}).Execute()
119+
if body != nil {
120+
defer body.Body.Close()
121+
}
122+
if err != nil {
123+
resp.Diagnostics.AddError(
124+
"Unable to create Destination subscription",
125+
getError(err, body),
126+
)
127+
128+
return
129+
}
130+
131+
destinationSubscription := out.Data.GetDestinationSubscription()
132+
133+
var state models.DestinationSubscriptionState
134+
err = state.Fill(destinationSubscription)
135+
if err != nil {
136+
resp.Diagnostics.AddError(
137+
"Unable to populate Destination subscription state",
138+
err.Error(),
139+
)
140+
141+
return
142+
}
143+
144+
// This is to satisfy terraform requirements that the returned fields must match the input ones because new settings can be generated in the response
145+
state.Settings = plan.Settings
146+
147+
// Set state to fully populated data
148+
diags = resp.State.Set(ctx, state)
149+
resp.Diagnostics.Append(diags...)
150+
if resp.Diagnostics.HasError() {
151+
return
152+
}
153+
}
154+
155+
func (r *destinationSubscriptionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
156+
var previousState models.DestinationSubscriptionState
157+
158+
diags := req.State.Get(ctx, &previousState)
159+
160+
resp.Diagnostics.Append(diags...)
161+
if resp.Diagnostics.HasError() {
162+
return
163+
}
164+
165+
out, body, err := r.client.DestinationsApi.GetSubscriptionFromDestination(r.authContext, previousState.DestinationID.ValueString(), previousState.ID.ValueString()).Execute()
166+
if body != nil {
167+
defer body.Body.Close()
168+
}
169+
if err != nil {
170+
resp.Diagnostics.AddError(
171+
"Unable to read Destination subscription",
172+
getError(err, body),
173+
)
174+
175+
return
176+
}
177+
178+
var state models.DestinationSubscriptionState
179+
180+
err = state.Fill(api.DestinationSubscription(out.Data.GetSubscription()))
181+
if err != nil {
182+
resp.Diagnostics.AddError(
183+
"Unable to populate Destination subscription state",
184+
err.Error(),
185+
)
186+
187+
return
188+
}
189+
190+
if !previousState.Settings.IsNull() && !previousState.Settings.IsUnknown() {
191+
state.Settings = previousState.Settings
192+
}
193+
194+
diags = resp.State.Set(ctx, &state)
195+
resp.Diagnostics.Append(diags...)
196+
if resp.Diagnostics.HasError() {
197+
return
198+
}
199+
}
200+
201+
func (r *destinationSubscriptionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
202+
var plan models.DestinationSubscriptionState
203+
diags := req.Plan.Get(ctx, &plan)
204+
resp.Diagnostics.Append(diags...)
205+
if resp.Diagnostics.HasError() {
206+
return
207+
}
208+
209+
var state models.DestinationSubscriptionState
210+
diags = req.State.Get(ctx, &state)
211+
resp.Diagnostics.Append(diags...)
212+
if resp.Diagnostics.HasError() {
213+
return
214+
}
215+
216+
var settings map[string]interface{}
217+
diags = plan.Settings.Unmarshal(&settings)
218+
resp.Diagnostics.Append(diags...)
219+
if resp.Diagnostics.HasError() {
220+
return
221+
}
222+
modelMap := api.NewModelMap(settings)
223+
224+
out, body, err := r.client.DestinationsApi.UpdateSubscriptionForDestination(r.authContext, state.DestinationID.ValueString(), state.ID.ValueString()).UpdateSubscriptionForDestinationAlphaInput(api.UpdateSubscriptionForDestinationAlphaInput{
225+
Input: api.Input{
226+
Name: plan.Name.ValueStringPointer(),
227+
Trigger: plan.Trigger.ValueStringPointer(),
228+
Enabled: plan.Enabled.ValueBoolPointer(),
229+
Settings: *api.NewNullableModelMap(modelMap),
230+
},
231+
}).Execute()
232+
if body != nil {
233+
defer body.Body.Close()
234+
}
235+
if err != nil {
236+
resp.Diagnostics.AddError(
237+
"Unable to update Destination subscription",
238+
getError(err, body),
239+
)
240+
241+
return
242+
}
243+
244+
err = state.Fill(api.DestinationSubscription(out.Data.GetSubscription()))
245+
if err != nil {
246+
resp.Diagnostics.AddError(
247+
"Unable to populate Destination subscription state",
248+
err.Error(),
249+
)
250+
251+
return
252+
}
253+
254+
// This is to satisfy terraform requirements that the returned fields must match the input ones because new settings can be generated in the response
255+
state.Settings = plan.Settings
256+
257+
diags = resp.State.Set(ctx, &state)
258+
resp.Diagnostics.Append(diags...)
259+
if resp.Diagnostics.HasError() {
260+
return
261+
}
262+
}
263+
264+
func (r *destinationSubscriptionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
265+
var config models.DestinationSubscriptionState
266+
diags := req.State.Get(ctx, &config)
267+
resp.Diagnostics.Append(diags...)
268+
if resp.Diagnostics.HasError() {
269+
return
270+
}
271+
272+
_, body, err := r.client.DestinationsApi.RemoveSubscriptionFromDestination(r.authContext, config.DestinationID.ValueString(), config.ID.ValueString()).Execute()
273+
if body != nil {
274+
defer body.Body.Close()
275+
}
276+
if err != nil {
277+
resp.Diagnostics.AddError(
278+
"Unable to delete Destination subscription",
279+
getError(err, body),
280+
)
281+
282+
return
283+
}
284+
}
285+
286+
func (r *destinationSubscriptionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
287+
idParts := strings.Split(req.ID, ":")
288+
289+
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
290+
resp.Diagnostics.AddError(
291+
"Unexpected Import Identifier",
292+
fmt.Sprintf("Expected import identifier with format: <destination_id>:<subscription_id>. Got: %q", req.ID),
293+
)
294+
295+
return
296+
}
297+
298+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("destination_id"), idParts[0])...)
299+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...)
300+
}
301+
302+
func (r *destinationSubscriptionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
303+
if req.ProviderData == nil {
304+
return
305+
}
306+
307+
config, ok := req.ProviderData.(*ClientInfo)
308+
if !ok {
309+
resp.Diagnostics.AddError(
310+
"Unexpected Resource Configure Type",
311+
fmt.Sprintf("Expected ClientInfo, got: %T. Please report this issue to the provider developers.", req.ProviderData),
312+
)
313+
314+
return
315+
}
316+
317+
r.client = config.client
318+
r.authContext = config.authContext
319+
}

0 commit comments

Comments
 (0)