Skip to content

Commit 93c85e7

Browse files
authored
Move RETL schedule config to destination subscription (#145)
1 parent e6e934f commit 93c85e7

File tree

8 files changed

+557
-45
lines changed

8 files changed

+557
-45
lines changed

docs/resources/destination_subscription.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,20 @@ resource "segment_destination_subscription" "send_to_webhook" {
7474
### Optional
7575

7676
- `model_id` (String) The unique identifier for the linked ReverseETLModel, if this part of a Reverse ETL connection.
77+
- `reverse_etl_schedule` (Attributes) (Reverse ETL only) The schedule for the subscription being attached to ReverseETL model. (see [below for nested schema](#nestedatt--reverse_etl_schedule))
7778

7879
### Read-Only
7980

8081
- `action_slug` (String) The URL-friendly key for the associated Destination action.
8182
- `id` (String) The unique identifier for the subscription.
83+
84+
<a id="nestedatt--reverse_etl_schedule"></a>
85+
### Nested Schema for `reverse_etl_schedule`
86+
87+
Required:
88+
89+
- `strategy` (String) Strategy supports three modes: PERIODIC, SPECIFIC_DAYS, or MANUAL.
90+
91+
Optional:
92+
93+
- `config` (String) Configures the schedule for the subscription.

docs/resources/reverse_etl_model.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,13 @@ resource "segment_reverse_etl_model" "example" {
6868
- `name` (String) A short, human-readable description of the Model.
6969
- `query` (String) The SQL query that will be executed to extract data from the connected Source.
7070
- `query_identifier_column` (String) Indicates the column named in `query` that should be used to uniquely identify the extracted records.
71-
- `schedule_config` (String) Depending on the chosen strategy, configures the schedule for this model.
72-
- `schedule_strategy` (String) Determines the strategy used for triggering syncs, which will be used in conjunction with scheduleConfig.
7371
- `source_id` (String) Indicates which Source to attach this model to.
7472

73+
### Optional
74+
75+
- `schedule_config` (String, Deprecated) Depending on the chosen strategy, configures the schedule for this model.
76+
- `schedule_strategy` (String, Deprecated) Determines the strategy used for triggering syncs, which will be used in conjunction with scheduleConfig.
77+
7578
### Read-Only
7679

7780
- `id` (String) The unique identifier for the model.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
1212
github.com/hashicorp/terraform-plugin-go v0.24.0
1313
github.com/hashicorp/terraform-plugin-testing v1.10.0
14-
github.com/segmentio/public-api-sdk-go v0.0.0-20240909200753-311bb8d791a2
14+
github.com/segmentio/public-api-sdk-go v0.0.0-20241025180535-501a23c07559
1515
gotest.tools/gotestsum v1.12.0
1616
)
1717

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3V
187187
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
188188
github.com/segmentio/public-api-sdk-go v0.0.0-20240909200753-311bb8d791a2 h1:vlKTelJ32DPBuiiSx2PJaxN9jJd3OFK2avHU/XR/qB8=
189189
github.com/segmentio/public-api-sdk-go v0.0.0-20240909200753-311bb8d791a2/go.mod h1:yKkoPfcOkkYjiZQj4lRWxji0Qwc6ncNEf7wCfywochY=
190+
github.com/segmentio/public-api-sdk-go v0.0.0-20241017001201-fbbdab459db8 h1:pYJu97HA0FVdy+WCqQbS/baDzXxg2q0Vl+akD6SUBSI=
191+
github.com/segmentio/public-api-sdk-go v0.0.0-20241017001201-fbbdab459db8/go.mod h1:yKkoPfcOkkYjiZQj4lRWxji0Qwc6ncNEf7wCfywochY=
192+
github.com/segmentio/public-api-sdk-go v0.0.0-20241025180535-501a23c07559 h1:6jgXPksz5bEJUMbhp4biSIViye/Os2yfAOx9yy44e1g=
193+
github.com/segmentio/public-api-sdk-go v0.0.0-20241025180535-501a23c07559/go.mod h1:yKkoPfcOkkYjiZQj4lRWxji0Qwc6ncNEf7wCfywochY=
190194
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
191195
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
192196
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=

internal/provider/destination_subscription_resource.go

Lines changed: 195 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ package provider
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"strings"
78

89
"github.com/segmentio/terraform-provider-segment/internal/provider/docs"
910
"github.com/segmentio/terraform-provider-segment/internal/provider/models"
1011

1112
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
13+
"github.com/hashicorp/terraform-plugin-framework/diag"
1214
"github.com/hashicorp/terraform-plugin-framework/path"
1315
"github.com/hashicorp/terraform-plugin-framework/resource"
1416
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1517
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1618
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
19+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1720

1821
"github.com/segmentio/public-api-sdk-go/api"
1922
)
@@ -91,12 +94,27 @@ func (r *destinationSubscriptionResource) Schema(_ context.Context, _ resource.S
9194
Description: `The customer settings for action fields. Only settings included in the configuration will be managed by Terraform.`,
9295
CustomType: jsontypes.NormalizedType{},
9396
},
97+
"reverse_etl_schedule": schema.SingleNestedAttribute{
98+
Optional: true,
99+
Description: "(Reverse ETL only) The schedule for the subscription being attached to ReverseETL model.",
100+
Attributes: map[string]schema.Attribute{
101+
"strategy": schema.StringAttribute{
102+
Required: true,
103+
Description: "Strategy supports three modes: PERIODIC, SPECIFIC_DAYS, or MANUAL.",
104+
},
105+
"config": schema.StringAttribute{
106+
Optional: true,
107+
Description: "Configures the schedule for the subscription.",
108+
CustomType: jsontypes.NormalizedType{},
109+
},
110+
},
111+
},
94112
},
95113
}
96114
}
97115

98116
func (r *destinationSubscriptionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
99-
var plan models.DestinationSubscriptionState
117+
var plan models.DestinationSubscriptionPlan
100118
diags := req.Plan.Get(ctx, &plan)
101119
resp.Diagnostics.Append(diags...)
102120
if resp.Diagnostics.HasError() {
@@ -110,6 +128,15 @@ func (r *destinationSubscriptionResource) Create(ctx context.Context, req resour
110128
return
111129
}
112130

131+
if !plan.ModelID.IsNull() && !plan.ModelID.IsUnknown() && (plan.ReverseETLSchedule.IsNull() || plan.ReverseETLSchedule.IsUnknown()) {
132+
resp.Diagnostics.AddError(
133+
"Reverse ETL model ID provided without reverse ETL schedule",
134+
"Reverse ETL model ID must be provided with a reverse ETL schedule",
135+
)
136+
137+
return
138+
}
139+
113140
out, body, err := r.client.DestinationsAPI.CreateDestinationSubscription(r.authContext, plan.DestinationID.ValueString()).CreateDestinationSubscriptionAlphaInput(api.CreateDestinationSubscriptionAlphaInput{
114141
Name: plan.Name.ValueString(),
115142
ActionId: plan.ActionID.ValueString(),
@@ -130,9 +157,39 @@ func (r *destinationSubscriptionResource) Create(ctx context.Context, req resour
130157
return
131158
}
132159

133-
destinationSubscription := out.Data.GetDestinationSubscription()
160+
resp.State.SetAttribute(ctx, path.Root("id"), out.Data.DestinationSubscription.Id)
161+
resp.State.SetAttribute(ctx, path.Root("destination_id"), out.Data.DestinationSubscription.DestinationId)
162+
163+
reverseETLSchedule, diags := getSchedule(ctx, plan.ReverseETLSchedule)
164+
if diags.HasError() {
165+
resp.Diagnostics.Append(diags...)
166+
167+
return
168+
}
169+
170+
updateOut, body, err := r.client.DestinationsAPI.UpdateSubscriptionForDestination(r.authContext, plan.DestinationID.ValueString(), out.Data.DestinationSubscription.Id).UpdateSubscriptionForDestinationAlphaInput(api.UpdateSubscriptionForDestinationAlphaInput{
171+
Input: api.DestinationSubscriptionUpdateInput{
172+
Name: plan.Name.ValueStringPointer(),
173+
Trigger: plan.Trigger.ValueStringPointer(),
174+
Enabled: plan.Enabled.ValueBoolPointer(),
175+
Settings: settings,
176+
ReverseETLModelId: plan.ModelID.ValueStringPointer(),
177+
ReverseETLSchedule: reverseETLSchedule,
178+
},
179+
}).Execute()
180+
if body != nil {
181+
defer body.Body.Close()
182+
}
183+
if err != nil {
184+
resp.Diagnostics.AddError(
185+
"Unable to update Destination subscription",
186+
getError(err, body),
187+
)
188+
189+
return
190+
}
134191

135-
resp.State.SetAttribute(ctx, path.Root("id"), destinationSubscription.Id)
192+
destinationSubscription := updateOut.Data.Subscription
136193

137194
var state models.DestinationSubscriptionState
138195
err = state.Fill(destinationSubscription)
@@ -203,7 +260,7 @@ func (r *destinationSubscriptionResource) Read(ctx context.Context, req resource
203260
}
204261

205262
func (r *destinationSubscriptionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
206-
var plan models.DestinationSubscriptionState
263+
var plan models.DestinationSubscriptionPlan
207264
diags := req.Plan.Get(ctx, &plan)
208265
resp.Diagnostics.Append(diags...)
209266
if resp.Diagnostics.HasError() {
@@ -224,12 +281,30 @@ func (r *destinationSubscriptionResource) Update(ctx context.Context, req resour
224281
return
225282
}
226283

284+
if !plan.ModelID.IsNull() && !plan.ModelID.IsUnknown() && (plan.ReverseETLSchedule.IsNull() || plan.ReverseETLSchedule.IsUnknown()) {
285+
resp.Diagnostics.AddError(
286+
"Reverse ETL model ID provided without reverse ETL schedule",
287+
"Reverse ETL model ID must be provided with a reverse ETL schedule",
288+
)
289+
290+
return
291+
}
292+
293+
reverseETLSchedule, diags := getSchedule(ctx, plan.ReverseETLSchedule)
294+
if diags.HasError() {
295+
resp.Diagnostics.Append(diags...)
296+
297+
return
298+
}
299+
227300
out, body, err := r.client.DestinationsAPI.UpdateSubscriptionForDestination(r.authContext, state.DestinationID.ValueString(), state.ID.ValueString()).UpdateSubscriptionForDestinationAlphaInput(api.UpdateSubscriptionForDestinationAlphaInput{
228301
Input: api.DestinationSubscriptionUpdateInput{
229-
Name: plan.Name.ValueStringPointer(),
230-
Trigger: plan.Trigger.ValueStringPointer(),
231-
Enabled: plan.Enabled.ValueBoolPointer(),
232-
Settings: settings,
302+
Name: plan.Name.ValueStringPointer(),
303+
Trigger: plan.Trigger.ValueStringPointer(),
304+
Enabled: plan.Enabled.ValueBoolPointer(),
305+
Settings: settings,
306+
ReverseETLModelId: plan.ModelID.ValueStringPointer(),
307+
ReverseETLSchedule: reverseETLSchedule,
233308
},
234309
}).Execute()
235310
if body != nil {
@@ -320,3 +395,115 @@ func (r *destinationSubscriptionResource) Configure(_ context.Context, req resou
320395
r.client = config.client
321396
r.authContext = config.authContext
322397
}
398+
399+
func getSchedule(ctx context.Context, planSchedule basetypes.ObjectValue) (*api.ReverseEtlScheduleDefinition, diag.Diagnostics) {
400+
var reverseETLSchedule *api.ReverseEtlScheduleDefinition
401+
var diags diag.Diagnostics
402+
if !planSchedule.IsNull() && !planSchedule.IsUnknown() {
403+
reverseETLSchedule = &api.ReverseEtlScheduleDefinition{}
404+
405+
wrappedReverseETLModelScheduleStrategy, err := planSchedule.Attributes()["strategy"].ToTerraformValue(ctx)
406+
if err != nil {
407+
diags.AddError(
408+
"Unable to decode reverse ETL schedule strategy",
409+
err.Error(),
410+
)
411+
412+
return nil, diags
413+
}
414+
415+
var reverseETLModelScheduleStrategy string
416+
err = wrappedReverseETLModelScheduleStrategy.As(&reverseETLModelScheduleStrategy)
417+
if err != nil {
418+
diags.AddError(
419+
"Unable to decode reverse ETL schedule strategy",
420+
err.Error(),
421+
)
422+
423+
return nil, diags
424+
}
425+
426+
reverseETLSchedule.Strategy = reverseETLModelScheduleStrategy
427+
428+
wrappedReverseETLModelScheduleConfig, err := planSchedule.Attributes()["config"].ToTerraformValue(ctx)
429+
if err != nil {
430+
diags.AddError(
431+
"Unable to decode reverse ETL schedule config",
432+
err.Error(),
433+
)
434+
435+
return nil, diags
436+
}
437+
438+
if !wrappedReverseETLModelScheduleConfig.IsNull() && wrappedReverseETLModelScheduleConfig.IsKnown() {
439+
if reverseETLSchedule.Strategy == "PERIODIC" {
440+
reverseETLModelScheduleConfig := api.ReverseEtlPeriodicScheduleConfig{}
441+
var config string
442+
err = wrappedReverseETLModelScheduleConfig.As(&config)
443+
if err != nil {
444+
diags.AddError(
445+
"Unable to decode reverse ETL schedule config",
446+
err.Error(),
447+
)
448+
449+
return nil, diags
450+
}
451+
452+
err = json.Unmarshal([]byte(config), &reverseETLModelScheduleConfig)
453+
if err != nil {
454+
diags.AddError(
455+
"Unable to decode reverse ETL schedule config",
456+
err.Error(),
457+
)
458+
459+
return nil, diags
460+
}
461+
462+
reverseETLSchedule.Config = *api.NewNullableConfig(&api.Config{
463+
ReverseEtlPeriodicScheduleConfig: &reverseETLModelScheduleConfig,
464+
})
465+
} else if reverseETLSchedule.Strategy == "SPECIFIC_DAYS" {
466+
reverseETLModelScheduleConfig := api.ReverseEtlSpecificTimeScheduleConfig{}
467+
var config string
468+
err = wrappedReverseETLModelScheduleConfig.As(&config)
469+
if err != nil {
470+
diags.AddError(
471+
"Unable to decode reverse ETL schedule config",
472+
err.Error(),
473+
)
474+
475+
return nil, diags
476+
}
477+
478+
err = json.Unmarshal([]byte(config), &reverseETLModelScheduleConfig)
479+
if err != nil {
480+
diags.AddError(
481+
"Unable to decode reverse ETL schedule config",
482+
err.Error(),
483+
)
484+
485+
return nil, diags
486+
}
487+
488+
reverseETLSchedule.Config = *api.NewNullableConfig(&api.Config{
489+
ReverseEtlSpecificTimeScheduleConfig: &reverseETLModelScheduleConfig,
490+
})
491+
} else if reverseETLSchedule.Strategy == "MANUAL" {
492+
diags.AddError(
493+
"Manual reverse ETL schedule strategy does not require a config",
494+
"Manual reverse ETL schedule strategy does not require a config",
495+
)
496+
reverseETLSchedule.Config = *api.NewNullableConfig(nil)
497+
} else {
498+
diags.AddError(
499+
"Unsupported reverse ETL schedule strategy",
500+
fmt.Sprintf("Strategy %q is not supported", reverseETLSchedule.Strategy),
501+
)
502+
503+
return nil, diags
504+
}
505+
}
506+
}
507+
508+
return reverseETLSchedule, diags
509+
}

0 commit comments

Comments
 (0)