Skip to content

Commit d1f97a1

Browse files
committed
fix(okms_secret): detect drift on data change
Signed-off-by: alexGNX <[email protected]>
1 parent 42d688c commit d1f97a1

File tree

1 file changed

+81
-6
lines changed

1 file changed

+81
-6
lines changed

ovh/resource_okms_secret.go

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,28 +82,75 @@ func (r *okmsSecretResource) Create(ctx context.Context, req resource.CreateRequ
8282
}
8383

8484
func (r *okmsSecretResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
85-
var data, responseData OkmsSecretModel
85+
var secretFromState, currentSecret OkmsSecretModel
8686

8787
// Read Terraform prior state data into the model
88-
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
88+
resp.Diagnostics.Append(req.State.Get(ctx, &secretFromState)...)
8989
if resp.Diagnostics.HasError() {
9090
return
9191
}
9292

93-
endpoint := "/v2/okms/resource/" + url.PathEscape(data.OkmsId.ValueString()) + "/secret/" + url.PathEscape(data.Path.ValueString()) + ""
93+
endpoint := "/v2/okms/resource/" + url.PathEscape(secretFromState.OkmsId.ValueString()) + "/secret/" + url.PathEscape(secretFromState.Path.ValueString())
9494

95-
if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil {
95+
if err := r.config.OVHClient.Get(endpoint, &currentSecret); err != nil {
9696
resp.Diagnostics.AddError(
9797
fmt.Sprintf("Error calling Get %s", endpoint),
9898
err.Error(),
9999
)
100100
return
101101
}
102102

103-
data.MergeWith(&responseData)
103+
// Update metadata and IAM from API response
104+
secretFromState.Metadata = currentSecret.Metadata
105+
secretFromState.Iam = currentSecret.Iam
106+
107+
// Fetch the current version details including data for drift detection
108+
currentVersion := currentSecret.Metadata.CurrentVersion.ValueInt64()
109+
versionEndpoint := "/v2/okms/resource/" + url.PathEscape(secretFromState.OkmsId.ValueString()) + "/secret/" + url.PathEscape(secretFromState.Path.ValueString()) + "/version/" + fmt.Sprintf("%d", currentVersion) + "?includeData=true"
110+
111+
var currentSecretVersion struct {
112+
Id *int64 `json:"id"`
113+
CreatedAt *string `json:"createdAt"`
114+
State *string `json:"state"`
115+
DeactivatedAt *string `json:"deactivatedAt"`
116+
Data json.RawMessage `json:"data"`
117+
}
118+
if err := r.config.OVHClient.Get(versionEndpoint, &currentSecretVersion); err == nil {
119+
// Update version computed fields
120+
if currentSecretVersion.Id != nil {
121+
secretFromState.Version.Id = ovhtypes.NewTfInt64Value(*currentSecretVersion.Id)
122+
}
123+
if currentSecretVersion.CreatedAt != nil {
124+
secretFromState.Version.CreatedAt = ovhtypes.NewTfStringValue(*currentSecretVersion.CreatedAt)
125+
}
126+
if currentSecretVersion.State != nil {
127+
secretFromState.Version.State = ovhtypes.NewTfStringValue(*currentSecretVersion.State)
128+
}
129+
if currentSecretVersion.DeactivatedAt != nil {
130+
secretFromState.Version.DeactivatedAt = ovhtypes.NewTfStringValue(*currentSecretVersion.DeactivatedAt)
131+
} else {
132+
secretFromState.Version.DeactivatedAt = ovhtypes.NewTfStringNull()
133+
}
134+
135+
// Check version state and data for drift detection
136+
if currentSecretVersion.State != nil && *currentSecretVersion.State == "DELETED" {
137+
// Version is deleted - mark data as empty to force drift detection
138+
secretFromState.Version.Data = ovhtypes.NewTfStringValue("")
139+
} else if len(currentSecretVersion.Data) > 0 {
140+
// Version is active - compare data for drift
141+
actualData := string(currentSecretVersion.Data)
142+
configData := secretFromState.Version.Data.ValueString()
143+
144+
if !semanticJSONEqual(configData, actualData) {
145+
// Data differs - update state with actual value so Terraform shows drift
146+
secretFromState.Version.Data = ovhtypes.NewTfStringValue(actualData)
147+
}
148+
}
149+
}
150+
// Silently ignore errors - drift detection is best-effort
104151

105152
// Save updated data into Terraform state
106-
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
153+
resp.Diagnostics.Append(resp.State.Set(ctx, &secretFromState)...)
107154
}
108155

109156
func (r *okmsSecretResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
@@ -244,6 +291,34 @@ func buildMetadataPayload(meta *MetadataValue) map[string]any {
244291
return mp
245292
}
246293

294+
// semanticJSONEqual compares two JSON strings for semantic equality,
295+
// ignoring formatting differences like whitespace and key ordering.
296+
func semanticJSONEqual(a, b string) bool {
297+
var objA, objB interface{}
298+
299+
// Try to parse both as JSON
300+
if err := json.Unmarshal([]byte(a), &objA); err != nil {
301+
// Not valid JSON, fall back to string comparison
302+
return a == b
303+
}
304+
if err := json.Unmarshal([]byte(b), &objB); err != nil {
305+
// Not valid JSON, fall back to string comparison
306+
return a == b
307+
}
308+
309+
// Re-marshal both in canonical form and compare
310+
normalizedA, err := json.Marshal(objA)
311+
if err != nil {
312+
return a == b
313+
}
314+
normalizedB, err := json.Marshal(objB)
315+
if err != nil {
316+
return a == b
317+
}
318+
319+
return string(normalizedA) == string(normalizedB)
320+
}
321+
247322
// populateVersionComputedFields fills secret version attributes
248323
func populateVersionComputedFields(r *okmsSecretResource, model *OkmsSecretModel, okmsId, path string) {
249324
// If currentVersion unknown or zero, nothing to enrich

0 commit comments

Comments
 (0)