From 7e9f99071269d21b0d68e004adf52d276fac254d Mon Sep 17 00:00:00 2001
From: Ryo Kitagawa <ryo-kitagawa@m3.com>
Date: Mon, 25 Oct 2021 21:36:27 +0900
Subject: [PATCH 1/2] Add OwnerReference

---
 main.go | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/main.go b/main.go
index 98edbda..cca46f4 100644
--- a/main.go
+++ b/main.go
@@ -126,7 +126,7 @@ func getNamespaceAndName(s []string) (namespace, name string, ok bool) {
 }
 
 func newJob(ctx context.Context, clientset *kubernetes.Clientset, namespace, name string) (*batchv1.Job, error) {
-	jobSpec, err := newJobTemplate(ctx, clientset, namespace, name)
+	jobSpec, ownerRef, err := newJobTemplate(ctx, clientset, namespace, name)
 	if err != nil {
 		return nil, err
 	}
@@ -141,18 +141,19 @@ func newJob(ctx context.Context, clientset *kubernetes.Clientset, namespace, nam
 			Kind:       "Job",
 		},
 		ObjectMeta: metav1.ObjectMeta{
-			Namespace: namespace,
-			Name:      fmt.Sprintf("%s-%s", name, suffix),
+			Namespace:       namespace,
+			Name:            fmt.Sprintf("%s-%s", name, suffix),
+			OwnerReferences: []metav1.OwnerReference{ownerRef},
 		},
 		Spec: jobSpec,
 	}
 	return job, nil
 }
 
-func newJobTemplate(ctx context.Context, clientset *kubernetes.Clientset, namespace, name string) (batchv1.JobSpec, error) {
+func newJobTemplate(ctx context.Context, clientset *kubernetes.Clientset, namespace, name string) (jobSpec batchv1.JobSpec, ownerRef metav1.OwnerReference, err error) {
 	v, err := clientset.ServerVersion()
 	if err != nil {
-		return batchv1.JobSpec{}, fmt.Errorf("failed to get serverVersion: %w", err)
+		return jobSpec, ownerRef, fmt.Errorf("failed to get serverVersion: %w", err)
 	}
 
 	// When kubernetes version is 1.21 or higher, use batchv1.CronJob.
@@ -160,16 +161,28 @@ func newJobTemplate(ctx context.Context, clientset *kubernetes.Clientset, namesp
 	if isCronJobGA(v) {
 		cj, err := clientset.BatchV1().CronJobs(namespace).Get(ctx, name, metav1.GetOptions{})
 		if err != nil {
-			return batchv1.JobSpec{}, err
+			return jobSpec, ownerRef, err
 		}
-		return cj.Spec.JobTemplate.Spec, nil
+		ownerRef := metav1.OwnerReference{
+			APIVersion: "batch/v1",
+			Kind:       "CronJob",
+			Name:       cj.GetName(),
+			UID:        cj.GetUID(),
+		}
+		return cj.Spec.JobTemplate.Spec, ownerRef, nil
 	}
 
 	cj, err := clientset.BatchV1beta1().CronJobs(namespace).Get(ctx, name, metav1.GetOptions{})
 	if err != nil {
-		return batchv1.JobSpec{}, err
+		return jobSpec, ownerRef, err
+	}
+	ownerRef = metav1.OwnerReference{
+		APIVersion: "batch/v1beta1",
+		Kind:       "CronJob",
+		Name:       cj.GetName(),
+		UID:        cj.GetUID(),
 	}
-	return cj.Spec.JobTemplate.Spec, nil
+	return cj.Spec.JobTemplate.Spec, ownerRef, nil
 }
 
 func isCronJobGA(v *version.Info) bool {

From e38b750800291fb44e08519717d6ddf4e934e114 Mon Sep 17 00:00:00 2001
From: Ryo Kitagawa <ryo-kitagawa@m3.com>
Date: Mon, 20 Dec 2021 14:27:25 +0900
Subject: [PATCH 2/2] fix: change ownerReferences to be commented out in yaml

---
 go.mod       |  1 +
 main.go      | 49 ++++++++++++++++++++++++++++++++--------
 main_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 105 insertions(+), 9 deletions(-)

diff --git a/go.mod b/go.mod
index b7dcffc..9514f6c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.22
 
 require (
 	github.com/goccy/go-yaml v1.11.3
+	github.com/google/go-cmp v0.6.0
 	github.com/mattn/go-tty v0.0.5
 	k8s.io/api v0.29.3
 	k8s.io/apimachinery v0.29.3
diff --git a/main.go b/main.go
index cca46f4..f624385 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"bytes"
 	"context"
 	"crypto/rand"
 	"flag"
@@ -8,6 +9,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	"github.com/mattn/go-tty"
@@ -164,10 +166,11 @@ func newJobTemplate(ctx context.Context, clientset *kubernetes.Clientset, namesp
 			return jobSpec, ownerRef, err
 		}
 		ownerRef := metav1.OwnerReference{
-			APIVersion: "batch/v1",
-			Kind:       "CronJob",
-			Name:       cj.GetName(),
-			UID:        cj.GetUID(),
+			APIVersion:         "batch/v1",
+			Kind:               "CronJob",
+			Name:               cj.GetName(),
+			UID:                cj.GetUID(),
+			BlockOwnerDeletion: toPtr(true),
 		}
 		return cj.Spec.JobTemplate.Spec, ownerRef, nil
 	}
@@ -177,10 +180,11 @@ func newJobTemplate(ctx context.Context, clientset *kubernetes.Clientset, namesp
 		return jobSpec, ownerRef, err
 	}
 	ownerRef = metav1.OwnerReference{
-		APIVersion: "batch/v1beta1",
-		Kind:       "CronJob",
-		Name:       cj.GetName(),
-		UID:        cj.GetUID(),
+		APIVersion:         "batch/v1beta1",
+		Kind:               "CronJob",
+		Name:               cj.GetName(),
+		UID:                cj.GetUID(),
+		BlockOwnerDeletion: toPtr(true),
 	}
 	return cj.Spec.JobTemplate.Spec, ownerRef, nil
 }
@@ -227,7 +231,7 @@ func createJobWithFileName(filename *string, job *batchv1.Job) error {
 }
 
 func createJob(f *os.File, job *batchv1.Job) error {
-	data, err := yaml.Marshal(job)
+	data, err := jobToYaml(job)
 	if err != nil {
 		return err
 	}
@@ -282,3 +286,30 @@ func createJob(f *os.File, job *batchv1.Job) error {
 	}
 	return nil
 }
+
+func jobToYaml(job *batchv1.Job) ([]byte, error) {
+	// Marshal with ownerReferences commented out
+	ownerRefs, err := yaml.Marshal(map[string]any{"ownerReferences": job.ObjectMeta.OwnerReferences})
+	if err != nil {
+		return nil, err
+	}
+
+	job.ObjectMeta.OwnerReferences = nil
+	data, err := yaml.Marshal(job)
+	if err != nil {
+		return nil, err
+	}
+
+	commentForOwnerRefs := "  # "
+	commentedOwnerRefs := commentForOwnerRefs + strings.ReplaceAll(string(ownerRefs), "\n", "\n"+commentForOwnerRefs)
+	commentedOwnerRefs = strings.TrimSuffix(commentedOwnerRefs, commentForOwnerRefs)
+	namespaceInd := bytes.Index(data, []byte("namespace: "))
+	namespaceLineInd := bytes.Index(data[namespaceInd:], []byte("\n")) + namespaceInd
+
+	data = slices.Insert(data, namespaceLineInd+1, []byte(commentedOwnerRefs)...)
+	return data, nil
+}
+
+func toPtr[T any](t T) *T {
+	return &t
+}
diff --git a/main_test.go b/main_test.go
index ef3b3a0..d539ce1 100644
--- a/main_test.go
+++ b/main_test.go
@@ -3,6 +3,9 @@ package main
 import (
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+	batchv1 "k8s.io/api/batch/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/version"
 )
 
@@ -104,3 +107,64 @@ func TestIsCronJobGA(t *testing.T) {
 		})
 	}
 }
+
+func TestJobToYaml(t *testing.T) {
+	tests := map[string]struct {
+		job    *batchv1.Job
+		expect []byte
+	}{
+		"should ownereReferences to commented out": {
+			job: &batchv1.Job{
+				TypeMeta: metav1.TypeMeta{
+					APIVersion: "batch/v1",
+					Kind:       "Job",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+					OwnerReferences: []metav1.OwnerReference{
+						{
+							APIVersion:         "batch/v1",
+							BlockOwnerDeletion: toPtr(true),
+							Kind:               "CronJob",
+							Name:               "test",
+							UID:                "",
+						},
+					},
+				},
+			},
+			expect: []byte(`apiVersion: batch/v1
+kind: Job
+metadata:
+  creationTimestamp: null
+  name: test
+  namespace: default
+  # ownerReferences:
+  # - apiVersion: batch/v1
+  #   blockOwnerDeletion: true
+  #   kind: CronJob
+  #   name: test
+  #   uid: ""
+spec:
+  template:
+    metadata:
+      creationTimestamp: null
+    spec:
+      containers: null
+status: {}
+`),
+		},
+	}
+
+	for n, tt := range tests {
+		t.Run(n, func(t *testing.T) {
+			got, err := jobToYaml(tt.job)
+			if err != nil {
+				t.Fatalf("jobToYaml got error: %v", err)
+			}
+			if diff := cmp.Diff(tt.expect, got); diff != "" {
+				t.Errorf("jobToYaml result diff (-expect, +got)\n%s", diff)
+			}
+		})
+	}
+}