Skip to content

Commit aa8caeb

Browse files
authored
Merge pull request #649 from NetApp/647-bug-netapp-ontap_snapmirror_policy-resource
647 - bug - netapp-ontap_snapmirror_policy resource
2 parents d3252eb + 5148d85 commit aa8caeb

9 files changed

Lines changed: 229 additions & 162 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 2.6.0 (2026-xx-xx)
2+
3+
BUG FIXES:
4+
5+
- **netapp-ontap_snapmirror_policy resource**: fixed issue with create when `retention` is not set in config ([#647](https://github.com/NetApp/terraform-provider-netapp-ontap/issues/647))
6+
17
# 2.5.0 (2026-03-12)
28

39
FEATURES:

examples/resources/netapp-ontap_snapmiror_policy/provider.tf

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../netapp-ontap_s3_user/provider.tf
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../netapp-ontap_s3_user/terraform.tfvars

examples/resources/netapp-ontap_snapmiror_policy/variables.tf

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../netapp-ontap_s3_user/variables.tf

internal/provider/snapmirror/snapmirror_policy_resource.go

Lines changed: 178 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -298,30 +298,67 @@ func (r *SnapmirrorPolicyResource) Read(ctx context.Context, req resource.ReadRe
298298
data.CreateSnapshotOnSource = types.BoolValue(restInfo.CreateSnapshotOnSource)
299299
data.ID = types.StringValue(restInfo.UUID)
300300

301-
// if len(restInfo.Retention) == 0 {
302-
if restInfo.Retention == nil {
303-
data.Retention = nil
304-
} else {
305-
data.Retention = []RetentionModel{}
306-
for _, item := range restInfo.Retention {
307-
var retention RetentionModel
308-
// conver count from string to int
309-
count, err := strconv.Atoi(item.Count)
310-
if err != nil {
311-
errorHandler.MakeAndReportError("Decode count error", "snapmirror_policy retention count is not valid")
312-
return
313-
}
314-
retention.Count = types.Int64Value(int64(count))
315-
if item.CreationSchedule.Name != "" {
316-
retention.CreationScheduleName = types.StringValue(item.CreationSchedule.Name)
317-
}
318-
if item.Label != "" {
319-
retention.Label = types.StringValue(item.Label)
301+
// preserve a null/empty retention in state unless the user configured rules
302+
if len(data.Retention) > 0 {
303+
if restInfo.Retention == nil {
304+
data.Retention = nil
305+
} else {
306+
// Match by label and preserve the configured order. This also avoids
307+
// persisting any additional default rule(s) returned by the API.
308+
restRetentionByLabel := make(map[string]interfaces.RetentionGetRawDataModel, len(restInfo.Retention))
309+
for _, item := range restInfo.Retention {
310+
if item.Label == "" {
311+
continue
312+
}
313+
// don't persist default retention rule returned by ONTAP
314+
if item.Label == "sm_created" && item.Count == "1" {
315+
continue
316+
}
317+
restRetentionByLabel[item.Label] = item
320318
}
321-
if item.Prefix != "" {
322-
retention.Prefix = types.StringValue(item.Prefix)
319+
320+
configuredRetention := data.Retention
321+
data.Retention = make([]RetentionModel, 0, len(configuredRetention))
322+
for _, configured := range configuredRetention {
323+
retention := configured
324+
// store unknown optional values as unset in state
325+
if retention.Count.IsUnknown() {
326+
retention.Count = types.Int64Null()
327+
}
328+
if retention.CreationScheduleName.IsUnknown() {
329+
retention.CreationScheduleName = types.StringNull()
330+
}
331+
if retention.Prefix.IsUnknown() {
332+
retention.Prefix = types.StringNull()
333+
}
334+
335+
// drop the default rule if it already exists in prior state
336+
if retention.Label.ValueString() == "sm_created" && (retention.Count.IsNull() || retention.Count.ValueInt64() == 1) {
337+
continue
338+
}
339+
configuredLabel := retention.Label.ValueString()
340+
restItem, ok := restRetentionByLabel[configuredLabel]
341+
if !ok {
342+
data.Retention = append(data.Retention, retention)
343+
continue
344+
}
345+
retention.Label = types.StringValue(restItem.Label)
346+
if !retention.Count.IsNull() {
347+
count, err := strconv.Atoi(restItem.Count)
348+
if err != nil {
349+
errorHandler.MakeAndReportError("Decode count error", "snapmirror_policy retention count is not valid")
350+
return
351+
}
352+
retention.Count = types.Int64Value(int64(count))
353+
}
354+
if !retention.CreationScheduleName.IsNull() && !retention.CreationScheduleName.IsUnknown() && restItem.CreationSchedule.Name != "" {
355+
retention.CreationScheduleName = types.StringValue(restItem.CreationSchedule.Name)
356+
}
357+
if !retention.Prefix.IsNull() && !retention.Prefix.IsUnknown() && restItem.Prefix != "" {
358+
retention.Prefix = types.StringValue(restItem.Prefix)
359+
}
360+
data.Retention = append(data.Retention, retention)
323361
}
324-
data.Retention = append(data.Retention, retention)
325362
}
326363
}
327364

@@ -415,30 +452,68 @@ func (r *SnapmirrorPolicyResource) Create(ctx context.Context, req resource.Crea
415452
tflog.Debug(ctx, fmt.Sprintf("create snapmirror policy get resource: %#v", resource))
416453
// Update the computed parameters
417454
data.ID = types.StringValue(resource.UUID)
418-
if resource.Retention == nil {
419-
data.Retention = nil
420-
tflog.Debug(ctx, fmt.Sprintf("create snapmirror policy retention is nil: %#v", data.Retention))
421-
} else {
422-
data.Retention = []RetentionModel{}
423-
for _, item := range resource.Retention {
424-
var retention RetentionModel
425-
// conver count from string to int
426-
count, err := strconv.Atoi(item.Count)
427-
if err != nil {
428-
errorHandler.MakeAndReportError("decode count error", "snapmirror_policy retention count is not valid")
429-
return
430-
}
431-
retention.Count = types.Int64Value(int64(count))
432-
if item.CreationSchedule.Name != "" {
433-
retention.CreationScheduleName = types.StringValue(item.CreationSchedule.Name)
434-
}
435-
if item.Label != "" {
436-
retention.Label = types.StringValue(item.Label)
455+
// preserve null/empty retention when it was not configured
456+
if len(data.Retention) > 0 {
457+
if resource.Retention == nil {
458+
data.Retention = nil
459+
tflog.Debug(ctx, fmt.Sprintf("create snapmirror policy retention is nil: %#v", data.Retention))
460+
} else {
461+
// Match by label and preserve the configured order to avoid including
462+
// any additional default rule(s) returned by the API.
463+
restRetentionByLabel := make(map[string]interfaces.RetentionGetRawDataModel, len(resource.Retention))
464+
for _, item := range resource.Retention {
465+
if item.Label == "" {
466+
continue
467+
}
468+
// don't persist default retention rule returned by ONTAP
469+
if item.Label == "sm_created" && item.Count == "1" {
470+
continue
471+
}
472+
restRetentionByLabel[item.Label] = item
437473
}
438-
if item.Prefix != "" {
439-
retention.Prefix = types.StringValue(item.Prefix)
474+
475+
configuredRetention := data.Retention
476+
data.Retention = make([]RetentionModel, 0, len(configuredRetention))
477+
for _, configured := range configuredRetention {
478+
retention := configured
479+
// store unknown optional values as unset in state
480+
if retention.Count.IsUnknown() {
481+
retention.Count = types.Int64Null()
482+
}
483+
if retention.CreationScheduleName.IsUnknown() {
484+
retention.CreationScheduleName = types.StringNull()
485+
}
486+
if retention.Prefix.IsUnknown() {
487+
retention.Prefix = types.StringNull()
488+
}
489+
490+
// drop the default rule if it already exists in prior state
491+
if retention.Label.ValueString() == "sm_created" && (retention.Count.IsNull() || retention.Count.ValueInt64() == 1) {
492+
continue
493+
}
494+
configuredLabel := retention.Label.ValueString()
495+
restItem, ok := restRetentionByLabel[configuredLabel]
496+
if !ok {
497+
data.Retention = append(data.Retention, retention)
498+
continue
499+
}
500+
retention.Label = types.StringValue(restItem.Label)
501+
if !retention.Count.IsNull() {
502+
count, err := strconv.Atoi(restItem.Count)
503+
if err != nil {
504+
errorHandler.MakeAndReportError("decode count error", "snapmirror_policy retention count is not valid")
505+
return
506+
}
507+
retention.Count = types.Int64Value(int64(count))
508+
}
509+
if !retention.CreationScheduleName.IsNull() && !retention.CreationScheduleName.IsUnknown() && restItem.CreationSchedule.Name != "" {
510+
retention.CreationScheduleName = types.StringValue(restItem.CreationSchedule.Name)
511+
}
512+
if !retention.Prefix.IsNull() && !retention.Prefix.IsUnknown() && restItem.Prefix != "" {
513+
retention.Prefix = types.StringValue(restItem.Prefix)
514+
}
515+
data.Retention = append(data.Retention, retention)
440516
}
441-
data.Retention = append(data.Retention, retention)
442517
}
443518
}
444519
data.Type = types.StringValue(resource.Type)
@@ -582,29 +657,67 @@ func (r *SnapmirrorPolicyResource) Update(ctx context.Context, req resource.Upda
582657
return
583658
}
584659

585-
if restInfo.Retention == nil {
586-
plan.Retention = nil
587-
} else {
588-
plan.Retention = []RetentionModel{}
589-
for _, item := range restInfo.Retention {
590-
var retention RetentionModel
591-
// conver count from string to int
592-
count, err := strconv.Atoi(item.Count)
593-
if err != nil {
594-
errorHandler.MakeAndReportError("decode count error", "snapmirror_policy retention count is not valid")
595-
return
596-
}
597-
retention.Count = types.Int64Value(int64(count))
598-
if item.CreationSchedule.Name != "" {
599-
retention.CreationScheduleName = types.StringValue(item.CreationSchedule.Name)
600-
}
601-
if item.Label != "" {
602-
retention.Label = types.StringValue(item.Label)
660+
// preserve null/empty retention when it was not configured
661+
if len(plan.Retention) > 0 {
662+
if restInfo.Retention == nil {
663+
plan.Retention = nil
664+
} else {
665+
// Match by label and preserve the configured order to avoid including
666+
// any additional default rule(s) returned by the API.
667+
restRetentionByLabel := make(map[string]interfaces.RetentionGetRawDataModel, len(restInfo.Retention))
668+
for _, item := range restInfo.Retention {
669+
if item.Label == "" {
670+
continue
671+
}
672+
// don't persist default retention rule returned by ONTAP
673+
if item.Label == "sm_created" && item.Count == "1" {
674+
continue
675+
}
676+
restRetentionByLabel[item.Label] = item
603677
}
604-
if item.Prefix != "" {
605-
retention.Prefix = types.StringValue(item.Prefix)
678+
679+
configuredRetention := plan.Retention
680+
plan.Retention = make([]RetentionModel, 0, len(configuredRetention))
681+
for _, configured := range configuredRetention {
682+
retention := configured
683+
// store unknown optional values as unset in state
684+
if retention.Count.IsUnknown() {
685+
retention.Count = types.Int64Null()
686+
}
687+
if retention.CreationScheduleName.IsUnknown() {
688+
retention.CreationScheduleName = types.StringNull()
689+
}
690+
if retention.Prefix.IsUnknown() {
691+
retention.Prefix = types.StringNull()
692+
}
693+
694+
// drop the default rule if it already exists in prior plan/state
695+
if retention.Label.ValueString() == "sm_created" && (retention.Count.IsNull() || retention.Count.ValueInt64() == 1) {
696+
continue
697+
}
698+
configuredLabel := retention.Label.ValueString()
699+
restItem, ok := restRetentionByLabel[configuredLabel]
700+
if !ok {
701+
plan.Retention = append(plan.Retention, retention)
702+
continue
703+
}
704+
retention.Label = types.StringValue(restItem.Label)
705+
if !retention.Count.IsNull() {
706+
count, err := strconv.Atoi(restItem.Count)
707+
if err != nil {
708+
errorHandler.MakeAndReportError("decode count error", "snapmirror_policy retention count is not valid")
709+
return
710+
}
711+
retention.Count = types.Int64Value(int64(count))
712+
}
713+
if !retention.CreationScheduleName.IsNull() && !retention.CreationScheduleName.IsUnknown() && restItem.CreationSchedule.Name != "" {
714+
retention.CreationScheduleName = types.StringValue(restItem.CreationSchedule.Name)
715+
}
716+
if !retention.Prefix.IsNull() && !retention.Prefix.IsUnknown() && restItem.Prefix != "" {
717+
retention.Prefix = types.StringValue(restItem.Prefix)
718+
}
719+
plan.Retention = append(plan.Retention, retention)
606720
}
607-
plan.Retention = append(plan.Retention, retention)
608721
}
609722
}
610723

0 commit comments

Comments
 (0)