Skip to content

Commit ff6c89b

Browse files
Fixed edge case to where we update ttl from 0 to another value
1 parent 49c28bd commit ff6c89b

File tree

2 files changed

+143
-13
lines changed

2 files changed

+143
-13
lines changed

kubernetes/resource_kubernetes_job_v1.go

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99
"log"
10+
"strconv"
1011
"time"
1112

1213
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -16,6 +17,7 @@ import (
1617
batchv1 "k8s.io/api/batch/v1"
1718
corev1 "k8s.io/api/core/v1"
1819
"k8s.io/apimachinery/pkg/api/errors"
20+
apierrors "k8s.io/apimachinery/pkg/api/errors"
1921
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022
pkgApi "k8s.io/apimachinery/pkg/types"
2123
"k8s.io/client-go/kubernetes"
@@ -55,6 +57,27 @@ func resourceKubernetesJobV1CustomizeDiff(ctx context.Context, d *schema.Resourc
5557
return nil
5658
}
5759

60+
// Retrieve old and new TTL values as strings
61+
oldTTLRaw, newTTLRaw := d.GetChange("spec.0.ttl_seconds_after_finished")
62+
63+
var oldTTLStr, newTTLStr string
64+
65+
if oldTTLRaw != nil {
66+
oldTTLStr, _ = oldTTLRaw.(string)
67+
}
68+
if newTTLRaw != nil {
69+
newTTLStr, _ = newTTLRaw.(string)
70+
}
71+
72+
oldTTLInt, err := strconv.Atoi(oldTTLStr)
73+
if err != nil {
74+
oldTTLInt = 0
75+
}
76+
newTTLInt, err := strconv.Atoi(newTTLStr)
77+
if err != nil {
78+
newTTLInt = 0
79+
}
80+
5881
conn, err := meta.(KubeClientsets).MainClientset()
5982
if err != nil {
6083
return err
@@ -65,23 +88,40 @@ func resourceKubernetesJobV1CustomizeDiff(ctx context.Context, d *schema.Resourc
6588
return err
6689
}
6790

68-
ttlAttr := d.Get("spec.0.ttl_seconds_after_finished")
69-
ttlSeconds, ok := ttlAttr.(int)
70-
if !ok || ttlSeconds != 0 {
71-
return nil
72-
}
73-
74-
// getting the job
91+
// Check if the Job exists
7592
_, err = conn.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})
7693
if err != nil {
77-
if errors.IsNotFound(err) {
78-
// Suppress diff
79-
d.Clear("spec")
80-
d.Clear("metadata")
94+
if apierrors.IsNotFound(err) {
95+
// Job is missing
96+
if oldTTLInt == 0 {
97+
if newTTLInt == 0 {
98+
// TTL remains 0; suppress diff to prevent recreation
99+
log.Printf("[DEBUG] Job %s not found and ttl_seconds_after_finished remains 0; suppressing diff", d.Id())
100+
d.Clear("spec")
101+
d.Clear("metadata")
102+
return nil
103+
} else {
104+
// TTL changed from 0 to non-zero; force recreation
105+
log.Printf("[DEBUG] Job %s not found and ttl_seconds_after_finished changed from 0 to %d; forcing recreation", d.Id(), newTTLInt)
106+
d.ForceNew("spec.0.ttl_seconds_after_finished")
107+
return nil
108+
}
109+
} else {
110+
return nil
111+
}
112+
} else {
113+
return err
114+
}
115+
} else {
116+
// Job exists
117+
if oldTTLInt == 0 && newTTLInt != 0 {
118+
// TTL changing from 0 to non-zero; force recreation
119+
log.Printf("[DEBUG] Job %s exists and ttl_seconds_after_finished changed from 0 to %d; forcing recreation", d.Id(), newTTLInt)
120+
d.ForceNew("spec.0.ttl_seconds_after_finished")
81121
return nil
82122
}
83-
return err
84123
}
124+
85125
return nil
86126
}
87127

@@ -219,6 +259,30 @@ func resourceKubernetesJobV1Update(ctx context.Context, d *schema.ResourceData,
219259
return diag.FromErr(err)
220260
}
221261

262+
// Attempt to get the Job
263+
_, err = conn.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})
264+
if err != nil {
265+
if apierrors.IsNotFound(err) {
266+
// Job is missing; cannot update
267+
ttlAttr := d.Get("spec.0.ttl_seconds_after_finished")
268+
ttlStr, _ := ttlAttr.(string)
269+
ttlInt, err := strconv.Atoi(ttlStr)
270+
if err != nil {
271+
ttlInt = 0
272+
}
273+
if ttlInt == 0 {
274+
// Job was deleted due to TTL = 0; nothing to update
275+
log.Printf("[INFO] Job %s not found but ttl_seconds_after_finished = 0; nothing to update", d.Id())
276+
return nil
277+
} else {
278+
// Job was deleted unexpectedly; return an error
279+
return diag.Errorf("Job %s not found; cannot update because it has been deleted", d.Id())
280+
}
281+
}
282+
return diag.Errorf("Error retrieving Job: %s", err)
283+
}
284+
285+
// Proceed with the update as usual
222286
ops := patchMetadata("metadata.0.", "/metadata/", d)
223287

224288
if d.HasChange("spec") {
@@ -250,7 +314,6 @@ func resourceKubernetesJobV1Update(ctx context.Context, d *schema.ResourceData,
250314
}
251315
return resourceKubernetesJobV1Read(ctx, d, meta)
252316
}
253-
254317
func resourceKubernetesJobV1Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
255318
conn, err := meta.(KubeClientsets).MainClientset()
256319
if err != nil {

kubernetes/resource_kubernetes_job_v1_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,48 @@ func TestAccKubernetesJobV1_customizeDiff_ttlZero(t *testing.T) {
271271
})
272272
}
273273

274+
func TestAccKubernetesJobV1_updateTTLFromZero(t *testing.T) {
275+
var conf batchv1.Job
276+
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10))
277+
imageName := busyboxImage
278+
resourceName := "kubernetes_job_v1.test"
279+
280+
resource.ParallelTest(t, resource.TestCase{
281+
PreCheck: func() {
282+
testAccPreCheck(t)
283+
skipIfClusterVersionLessThan(t, "1.21.0")
284+
},
285+
ProviderFactories: testAccProviderFactories,
286+
Steps: []resource.TestStep{
287+
// Step 1: Create the Job with ttl_seconds_after_finished = 0
288+
{
289+
Config: testAccKubernetesJobV1Config_customizeDiff_ttlZero(name, imageName),
290+
Check: resource.ComposeAggregateTestCheckFunc(
291+
testAccCheckKubernetesJobV1Exists(resourceName, &conf),
292+
resource.TestCheckResourceAttr(resourceName, "spec.0.ttl_seconds_after_finished", "0"),
293+
),
294+
},
295+
// Step 2: Wait for the Job to complete and be deleted
296+
{
297+
PreConfig: func() {
298+
time.Sleep(70 * time.Second)
299+
},
300+
Config: testAccKubernetesJobV1Config_customizeDiff_ttlZero(name, imageName),
301+
PlanOnly: true,
302+
ExpectNonEmptyPlan: false,
303+
},
304+
// Step 3: Update the Job to ttl_seconds_after_finished = 5
305+
{
306+
Config: testAccKubernetesJobV1Config_customizeDiff_ttlFive(name, imageName),
307+
Check: resource.ComposeAggregateTestCheckFunc(
308+
testAccCheckKubernetesJobV1Exists(resourceName, &conf),
309+
resource.TestCheckResourceAttr(resourceName, "spec.0.ttl_seconds_after_finished", "5"),
310+
),
311+
},
312+
},
313+
})
314+
}
315+
274316
func testAccCheckJobV1Waited(minDuration time.Duration) func(*terraform.State) error {
275317
// NOTE this works because this function is called when setting up the test
276318
// and the function it returns is called after the resource has been created
@@ -575,3 +617,28 @@ resource "kubernetes_job_v1" "test" {
575617
}
576618
`, name, imageName)
577619
}
620+
621+
func testAccKubernetesJobV1Config_customizeDiff_ttlFive(name, imageName string) string {
622+
return fmt.Sprintf(`
623+
resource "kubernetes_job_v1" "test" {
624+
metadata {
625+
name = "%s"
626+
}
627+
spec {
628+
ttl_seconds_after_finished = 5
629+
template {
630+
metadata {}
631+
spec {
632+
container {
633+
name = "wait-test"
634+
image = "%s"
635+
command = ["sleep", "60"]
636+
}
637+
restart_policy = "Never"
638+
}
639+
}
640+
}
641+
wait_for_completion = false
642+
}
643+
`, name, imageName)
644+
}

0 commit comments

Comments
 (0)