Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/hashicorp/terraform-plugin-go v0.26.0
github.com/hashicorp/terraform-plugin-testing v1.12.0
github.com/segmentio/public-api-sdk-go v0.0.0-20250113195817-34106b6e08dd
github.com/stretchr/testify v1.10.0
gotest.tools/gotestsum v1.13.0
)

Expand All @@ -19,8 +20,10 @@ require (
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
github.com/bitfield/gotestdox v0.2.2 // indirect
github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/cli v1.1.7 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/yuin/goldmark v1.7.7 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
Expand Down
13 changes: 11 additions & 2 deletions internal/provider/destination_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,18 @@ func (r *destinationResource) Read(ctx context.Context, req resource.ReadRequest
return
}

// This is to satisfy terraform requirements that the returned fields must match the input ones because new settings can be generated in the response
// Merge settings: keep config-defined settings while ignoring backend-generated ones not in config
if !previousState.Settings.IsNull() && !previousState.Settings.IsUnknown() {
state.Settings = previousState.Settings
mergedSettings, err := mergeSettings(previousState.Settings, state.Settings, false)
if err != nil {
resp.Diagnostics.AddError(
"Unable to merge Destination settings",
err.Error(),
)

return
}
state.Settings = mergedSettings
}

diags = resp.State.Set(ctx, &state)
Expand Down
12 changes: 11 additions & 1 deletion internal/provider/destination_subscription_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,18 @@ func (r *destinationSubscriptionResource) Read(ctx context.Context, req resource
return
}

// Merge settings: keep config-defined settings while ignoring backend-generated ones not in config
if !previousState.Settings.IsNull() && !previousState.Settings.IsUnknown() {
state.Settings = previousState.Settings
mergedSettings, err := mergeSettings(previousState.Settings, state.Settings, false)
if err != nil {
resp.Diagnostics.AddError(
"Unable to merge Destination subscription settings",
err.Error(),
)

return
}
state.Settings = mergedSettings
}

diags = resp.State.Set(ctx, &state)
Expand Down
12 changes: 11 additions & 1 deletion internal/provider/insert_function_instance_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,18 @@ func (r *insertFunctionInstanceResource) Read(ctx context.Context, req resource.
return
}

// Merge settings: keep config-defined settings while ignoring backend-generated ones not in config
if !previousState.Settings.IsNull() && !previousState.Settings.IsUnknown() {
state.Settings = previousState.Settings
mergedSettings, err := mergeSettings(previousState.Settings, state.Settings, false)
if err != nil {
resp.Diagnostics.AddError(
"Unable to merge Insert Function instance settings",
err.Error(),
)

return
}
state.Settings = mergedSettings
}

// This is to satisfy terraform requirements that the input fields must match the returned ones. The input FunctionID can be prefixed with "ifnd_" and the returned one is not.
Expand Down
12 changes: 11 additions & 1 deletion internal/provider/profiles_warehouse_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,18 @@ func (r *profilesWarehouseResource) Read(ctx context.Context, req resource.ReadR
return
}

// Merge settings: keep config-defined settings while ignoring backend-generated ones not in config
if !previousState.Settings.IsNull() && !previousState.Settings.IsUnknown() {
state.Settings = previousState.Settings
mergedSettings, err := mergeSettings(previousState.Settings, state.Settings, true)
if err != nil {
resp.Diagnostics.AddError(
"Unable to merge Profiles Warehouse settings",
err.Error(),
)

return
}
state.Settings = mergedSettings
}

diags = resp.State.Set(ctx, &state)
Expand Down
13 changes: 11 additions & 2 deletions internal/provider/source_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,18 @@ func (r *sourceResource) Read(ctx context.Context, req resource.ReadRequest, res
return
}

// This is to satisfy terraform requirements that the returned fields must match the input ones because new settings can be generated in the response
// Merge settings: keep config-defined settings while ignoring backend-generated ones not in config
if !previousState.Settings.IsNull() && !previousState.Settings.IsUnknown() {
state.Settings = previousState.Settings
mergedSettings, err := mergeSettings(previousState.Settings, state.Settings, false)
if err != nil {
resp.Diagnostics.AddError(
"Unable to merge Source settings",
err.Error(),
)

return
}
state.Settings = mergedSettings
}

diags = resp.State.Set(ctx, &state)
Expand Down
41 changes: 41 additions & 0 deletions internal/provider/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package provider
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/segmentio/terraform-provider-segment/internal/provider/models"
)

func getError(err error, body *http.Response) string {
Expand All @@ -23,3 +28,39 @@ func getError(err error, body *http.Response) string {

return err.Error() + "\n" + formattedBody.String()
}

// mergeSettings merges config settings with remote settings, preserving only the keys defined in config.
func mergeSettings(configSettings, remoteSettings jsontypes.Normalized, isWarehouse bool) (jsontypes.Normalized, error) {
var configMap map[string]interface{}
if diags := configSettings.Unmarshal(&configMap); diags.HasError() {
return jsontypes.NewNormalizedNull(), fmt.Errorf("failed to unmarshal config settings: %s", diags.Errors())
}

var remoteMap map[string]interface{}
if diags := remoteSettings.Unmarshal(&remoteMap); diags.HasError() {
return jsontypes.NewNormalizedNull(), fmt.Errorf("failed to unmarshal remote settings: %s", diags.Errors())
}

// Create merged map with only config-defined keys that exist in remote (to detect drift)
// Keys in config but not in remote are excluded (they don't exist or aren't supported)
merged := make(map[string]interface{})
for key := range configMap {
if isWarehouse && key == "password" { // Warehouses do not output password in the response
merged[key] = configMap[key]
} else if value, exists := remoteMap[key]; exists {
strValue, ok := value.(string)
if ok && strings.Contains(strValue, "•") { // If the secret is censored, do not update it
merged[key] = configMap[key]
} else {
merged[key] = value
}
}
}

result, err := models.GetSettings(merged)
if err != nil {
return jsontypes.Normalized{}, fmt.Errorf("failed to merge settings: %s", err.Error())
}

return result, nil
}
Loading
Loading