Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: %w", err)
}

return result, nil
}
Loading
Loading