Skip to content

Commit 6597a60

Browse files
Merge pull request #1414 from jfrog/JTFPR-1406
Preserve null package cleanup cron expression(#1408)
2 parents 373de9d + f7bcab7 commit 6597a60

3 files changed

Lines changed: 111 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### 12.11.7 (Jun 11, 2026). Tested on Artifactory 7.146.17 with Terraform 1.15.6 and OpenTofu 1.12.2
2+
3+
BUG FIXES:
4+
5+
* resource/artifactory_package_cleanup_policy: Preserve omitted `cron_expression` as null when Artifactory returns an empty string, avoiding an inconsistent result after apply. Issue: [#1406](https://github.com/jfrog/terraform-provider-artifactory/issues/1406)
6+
17
### 12.11.6 (Jun 9, 2026). Tested on Artifactory 7.146.15 with Terraform 1.15.5 and OpenTofu 1.12.1
28

39
IMPROVEMENTS:

pkg/artifactory/resource/configuration/resource_artifactory_package_cleanup_policy.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ func (r *PackageCleanupPolicyResourceModelV1) fromAPIModel(ctx context.Context,
390390

391391
r.Key = types.StringValue(apiModel.Key)
392392
r.Description = types.StringValue(apiModel.Description)
393-
r.CronExpression = types.StringValue(apiModel.CronExpression)
393+
r.CronExpression = normalizeEmptyAPIString(apiModel.CronExpression, r.CronExpression)
394394
r.DurationInMinutes = types.Int64Value(apiModel.DurationInMinutes)
395395
r.Enabled = types.BoolValue(apiModel.Enabled)
396396
r.SkipTrashcan = types.BoolValue(apiModel.SkipTrashcan)
@@ -529,6 +529,14 @@ func (r *PackageCleanupPolicyResourceModelV1) fromAPIModel(ctx context.Context,
529529
return diags
530530
}
531531

532+
func normalizeEmptyAPIString(apiValue string, priorValue types.String) types.String {
533+
if apiValue == "" && priorValue.IsNull() {
534+
return types.StringNull()
535+
}
536+
537+
return types.StringValue(apiValue)
538+
}
539+
532540
type PackageCleanupPolicyAPIModel struct {
533541
Key string `json:"key"`
534542
Description string `json:"description,omitempty"`
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) JFrog Ltd. (2026)
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configuration
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/hashicorp/terraform-plugin-framework/types"
22+
)
23+
24+
func TestPackageCleanupPolicyFromAPIModelPreservesNullCronExpression(t *testing.T) {
25+
model := PackageCleanupPolicyResourceModelV1{
26+
PackageCleanupPolicyResourceModelV0: PackageCleanupPolicyResourceModelV0{
27+
CronExpression: types.StringNull(),
28+
},
29+
}
30+
31+
diags := model.fromAPIModel(context.Background(), PackageCleanupPolicyAPIModel{
32+
CronExpression: "",
33+
SearchCriteria: packageCleanupPolicyTestSearchCriteria(),
34+
})
35+
36+
if diags.HasError() {
37+
t.Fatalf("unexpected diagnostics: %s", diags.Errors())
38+
}
39+
40+
if !model.CronExpression.IsNull() {
41+
t.Fatalf("expected null cron_expression, got %q", model.CronExpression.ValueString())
42+
}
43+
}
44+
45+
func TestPackageCleanupPolicyFromAPIModelKeepsConfiguredEmptyCronExpression(t *testing.T) {
46+
model := PackageCleanupPolicyResourceModelV1{
47+
PackageCleanupPolicyResourceModelV0: PackageCleanupPolicyResourceModelV0{
48+
CronExpression: types.StringValue(""),
49+
},
50+
}
51+
52+
diags := model.fromAPIModel(context.Background(), PackageCleanupPolicyAPIModel{
53+
CronExpression: "",
54+
SearchCriteria: packageCleanupPolicyTestSearchCriteria(),
55+
})
56+
57+
if diags.HasError() {
58+
t.Fatalf("unexpected diagnostics: %s", diags.Errors())
59+
}
60+
61+
if model.CronExpression.IsNull() {
62+
t.Fatal("expected configured empty cron_expression to stay as an empty string")
63+
}
64+
65+
if model.CronExpression.ValueString() != "" {
66+
t.Fatalf("expected empty cron_expression, got %q", model.CronExpression.ValueString())
67+
}
68+
}
69+
70+
func TestPackageCleanupPolicyFromAPIModelSetsNonEmptyCronExpression(t *testing.T) {
71+
model := PackageCleanupPolicyResourceModelV1{
72+
PackageCleanupPolicyResourceModelV0: PackageCleanupPolicyResourceModelV0{
73+
CronExpression: types.StringNull(),
74+
},
75+
}
76+
77+
diags := model.fromAPIModel(context.Background(), PackageCleanupPolicyAPIModel{
78+
CronExpression: "0 0 2 ? * MON-SAT *",
79+
SearchCriteria: packageCleanupPolicyTestSearchCriteria(),
80+
})
81+
82+
if diags.HasError() {
83+
t.Fatalf("unexpected diagnostics: %s", diags.Errors())
84+
}
85+
86+
if model.CronExpression.ValueString() != "0 0 2 ? * MON-SAT *" {
87+
t.Fatalf("expected non-empty cron_expression, got %q", model.CronExpression.ValueString())
88+
}
89+
}
90+
91+
func packageCleanupPolicyTestSearchCriteria() PackageCleanupPolicySearchCriteriaAPIModel {
92+
return PackageCleanupPolicySearchCriteriaAPIModel{
93+
PackageTypes: []string{"docker"},
94+
Repos: []string{"example-repo"},
95+
}
96+
}

0 commit comments

Comments
 (0)