Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kor for openSUSE:Factory checked in at 2023-12-11 21:50:30 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kor (Old) and /work/SRC/openSUSE:Factory/.kor.new.25432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kor" Mon Dec 11 21:50:30 2023 rev:8 rq:1132354 version:0.3.2 Changes: -------- --- /work/SRC/openSUSE:Factory/kor/kor.changes 2023-11-30 22:02:35.667891075 +0100 +++ /work/SRC/openSUSE:Factory/.kor.new.25432/kor.changes 2023-12-11 21:50:40.991821013 +0100 @@ -1,0 +2,11 @@ +Sun Dec 10 19:15:44 UTC 2023 - [email protected] + +- Update to version 0.3.2: + * Add labels to all templates. (#174) + * Fix command in templates. (#175) + * Fix exporterInterval variable. (#176) + * feat: find evicted pods (#173) + * feat: add jobs cleaner (#168) + * Add discord server to Readme (#171) + +------------------------------------------------------------------- Old: ---- kor-0.3.1.obscpio New: ---- kor-0.3.2.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kor.spec ++++++ --- /var/tmp/diff_new_pack.ORjqk5/_old 2023-12-11 21:50:42.527877897 +0100 +++ /var/tmp/diff_new_pack.ORjqk5/_new 2023-12-11 21:50:42.531878046 +0100 @@ -19,7 +19,7 @@ %define __arch_install_post export NO_BRP_STRIP_DEBUG=true Name: kor -Version: 0.3.1 +Version: 0.3.2 Release: 0 Summary: Tool to discover unused Kubernetes Resources License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.ORjqk5/_old 2023-12-11 21:50:42.575879675 +0100 +++ /var/tmp/diff_new_pack.ORjqk5/_new 2023-12-11 21:50:42.575879675 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/yonahd/kor</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.3.1</param> + <param name="revision">v0.3.2</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.ORjqk5/_old 2023-12-11 21:50:42.603880712 +0100 +++ /var/tmp/diff_new_pack.ORjqk5/_new 2023-12-11 21:50:42.607880860 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/yonahd/kor</param> - <param name="changesrevision">a459be1a0dfcf5d96ab0ed8c86ec8dc2d675de39</param></service></servicedata> + <param name="changesrevision">d5ad46865819f405591a7c37f34b631f8d756c89</param></service></servicedata> (No newline at EOF) ++++++ kor-0.3.1.obscpio -> kor-0.3.2.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/README.md new/kor-0.3.2/README.md --- old/kor-0.3.1/README.md 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/README.md 2023-12-09 22:04:13.000000000 +0100 @@ -2,6 +2,8 @@   [](https://codecov.io/gh/yonahd/kor) +[](https://discord.gg/ajptYPwcJY) + # Kor - Kubernetes Orphaned Resources Finder @@ -22,6 +24,8 @@ - PDBs - CRDs - PVs +- Pods +- Jobs  @@ -82,11 +86,13 @@ - `statefulsets` - Gets unused StatefulSets for the specified namespace or all namespaces. - `role` - Gets unused Roles for the specified namespace or all namespaces. - `hpa` - Gets unused HPAs for the specified namespace or all namespaces. +- `pods` - Gets unused Pods for the specified namespace or all namespaces. - `pvc` - Gets unused PVCs for the specified namespace or all namespaces. - `pv` - Gets unused PVs in the cluster(non namespaced resource). - `ingress` - Gets unused Ingresses for the specified namespace or all namespaces. - `pdb` - Gets unused PDBs for the specified namespace or all namespaces. - `crd` - Gets unused CRDs in the cluster(non namespaced resource). +- `jobs` - Gets unused jobs for the specified namespace or all namespaces. - `exporter` - Export Prometheus metrics. ### Supported Flags @@ -122,10 +128,10 @@ ## Supported resources and limitations -| Resource | What it looks for | Known False Positives â ï¸ | +| Resource | What it looks for | Known False Positives â ï¸ | |-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| | ConfigMaps | ConfigMaps not used in the following places:<br/>- Pods<br/>- Containers<br/>- ConfigMaps used through Volumes<br/>- ConfigMaps used through environment variables | ConfigMaps used by resources which don't explicitly state them in the config.<br/> e.g Grafana dashboards loaded dynamically OPA policies fluentd configs | -| Secrets | Secrets not used in the following places:<br/>- Pods<br/>- Containers<br/>- Secrets used through volumes<br/>- Secrets used through environment variables<br/>- Secrets used by Ingress TLS<br/>- Secrets used by ServiceAccounts | Secrets used by resources which don't explicitly state them in the config | +| Secrets | Secrets not used in the following places:<br/>- Pods<br/>- Containers<br/>- Secrets used through volumes<br/>- Secrets used through environment variables<br/>- Secrets used by Ingress TLS<br/>- Secrets used by ServiceAccounts | Secrets used by resources which don't explicitly state them in the config | | Services | Services with no endpoints | | | Deployments | Deployments with no Replicas | | | ServiceAccounts | ServiceAccounts unused by Pods<br/>ServiceAccounts unused by roleBinding or clusterRoleBinding | | @@ -137,7 +143,7 @@ | CRDs | CRDs not used the cluster | | | Pvs | PVs not bound to a PVC | | | Pdbs | PDBs not used in Deployments<br/> PDBs not used in StatefulSets | | - +| Jobs | Jobs status is completed | | ## Deleting Unused resources If you want to delete resources in an interactive way using Kor you can run: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/Chart.yaml new/kor-0.3.2/charts/kor/Chart.yaml --- old/kor-0.3.1/charts/kor/Chart.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/Chart.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -15,10 +15,10 @@ # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.2 +version: 0.1.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.2.8" +appVersion: "0.3.1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/cronjob.yaml new/kor-0.3.2/charts/kor/templates/cronjob.yaml --- old/kor-0.3.1/charts/kor/templates/cronjob.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/cronjob.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -4,6 +4,7 @@ metadata: name: {{ .Release.Name }} labels: + {{- include "kor.labels" . | nindent 4 }} app: {{ .Release.Name }} spec: schedule: {{ .Values.cronJob.schedule }} @@ -17,9 +18,13 @@ containers: - name: {{ .Release.Name }}-container image: {{ .Values.cronJob.image.repository }}:{{ .Values.cronJob.image.tag }} - command: ["/bin/sh", "-c"] + command: + {{- toYaml .Values.cronJob.command | nindent 16 }} + args: + {{- toYaml .Values.cronJob.args | nindent 16 }} {{- if .Values.cronJob.slackWebhookUrl }} - args: ["{{ .Values.cronJob.command }} --slack-webhook-url $SLACK_WEBHOOK_URL"] + - '--slack-webhook-url' + - "${SLACK_WEBHOOK_URL}" env: - name: SLACK_WEBHOOK_URL valueFrom: @@ -27,15 +32,16 @@ name: {{ .Release.Name }}-slack-webhook-url-secret key: slack-webhook-url {{- else if and .Values.cronJob.slackChannel .Values.cronJob.slackAuthToken }} - args: ["{{ .Values.cronJob.command }} --slack-channel {{ .Values.cronJob.slackChannel }} --slack-auth-token $SLACK_AUTH_TOKEN"] + - '--slack-channel' + - {{ .Values.cronJob.slackChannel | quote }} + - '--slack-auth-token' + - "${SLACK_AUTH_TOKEN}" env: - name: SLACK_AUTH_TOKEN valueFrom: secretKeyRef: name: {{ .Release.Name }}-slack-auth-token-secret key: slack-auth-token - {{- else }} - args: ["{{ .Values.cronJob.command }}"] {{- end }} {{- if .Values.cronJob.env }} env: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/deployment.yaml new/kor-0.3.2/charts/kor/templates/deployment.yaml --- old/kor-0.3.1/charts/kor/templates/deployment.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/deployment.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -4,6 +4,7 @@ metadata: name: {{ .Values.prometheusExporter.name }} labels: + {{- include "kor.labels" . | nindent 4 }} app: {{ .Values.prometheusExporter.name }} spec: {{- with .Values.prometheusExporter.deployment.imagePullSecrets }} @@ -27,8 +28,10 @@ {{ toYaml . | nindent 12 }} {{- end }} image: "{{ .Values.prometheusExporter.deployment.image.repository }}:{{ .Values.prometheusExporter.deployment.image.tag }}" - command: ["/bin/sh", "-c"] - args: ["{{ .Values.prometheusExporter.deployment.command }}"] + command: + {{- toYaml .Values.prometheusExporter.command | nindent 12 }} + args: + {{- toYaml .Values.prometheusExporter.args | nindent 12 }} ports: - containerPort: 8080 name: http @@ -40,7 +43,7 @@ {{- if .Values.prometheusExporter.exporterInterval }} env: - name: EXPORTER_INTERVAL - value: {{ .Values.prometheusExporter.exporterInterval }} + value: {{ .Values.prometheusExporter.exporterInterval | quote}} {{- end}} {{- with .Values.prometheusExporter.deployment.nodeSelector }} nodeSelector: @@ -63,4 +66,4 @@ securityContext: {{- toYaml . | nindent 8}} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/role.yaml new/kor-0.3.2/charts/kor/templates/role.yaml --- old/kor-0.3.1/charts/kor/templates/role.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/role.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -3,6 +3,8 @@ kind: Role metadata: name: {{ include "kor.serviceAccountName" . }}-read-resources-role + labels: + {{- include "kor.labels" . | nindent 4 }} rules: - apiGroups: ["*"] resources: @@ -32,6 +34,8 @@ kind: ClusterRole metadata: name: {{ include "kor.serviceAccountName" . }}-read-resources-clusterrole + labels: + {{- include "kor.labels" . | nindent 4 }} rules: - apiGroups: ["*"] resources: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/rolebinding.yaml new/kor-0.3.2/charts/kor/templates/rolebinding.yaml --- old/kor-0.3.1/charts/kor/templates/rolebinding.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/rolebinding.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -3,6 +3,8 @@ kind: RoleBinding metadata: name: {{ include "kor.serviceAccountName" . }}-read-resources-role-binding + labels: + {{- include "kor.labels" . | nindent 4 }} subjects: - kind: ServiceAccount name: {{ include "kor.serviceAccountName" . }} @@ -15,6 +17,8 @@ kind: ClusterRoleBinding metadata: name: {{ include "kor.serviceAccountName" . }}-read-resources-clusterrolebinding + labels: + {{- include "kor.labels" . | nindent 4 }} subjects: - kind: ServiceAccount name: {{ include "kor.serviceAccountName" . }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/secret.yaml new/kor-0.3.2/charts/kor/templates/secret.yaml --- old/kor-0.3.1/charts/kor/templates/secret.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/secret.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -4,6 +4,8 @@ kind: Secret metadata: name: {{ .Release.Name }}-slack-auth-token-secret + labels: + {{- include "kor.labels" . | nindent 4 }} type: Opaque data: slack-auth-token: {{ .Values.cronJob.slackAuthToken | b64enc | quote }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/service.yaml new/kor-0.3.2/charts/kor/templates/service.yaml --- old/kor-0.3.1/charts/kor/templates/service.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/service.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -8,6 +8,7 @@ prometheus.io/path: /metrics prometheus.io/port: "8080" labels: + {{- include "kor.labels" . | nindent 4 }} app: {{ .Values.prometheusExporter.name }} spec: type: {{ .Values.prometheusExporter.service.type }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/templates/servicemonitor.yaml new/kor-0.3.2/charts/kor/templates/servicemonitor.yaml --- old/kor-0.3.1/charts/kor/templates/servicemonitor.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/templates/servicemonitor.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -3,6 +3,7 @@ kind: ServiceMonitor metadata: labels: + {{- include "kor.labels" . | nindent 4 }} app: {{ .Values.prometheusExporter.name }} name: {{ .Values.prometheusExporter.name }} {{- if .Values.prometheusExporter.serviceMonitor.namespace }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/charts/kor/values.yaml new/kor-0.3.2/charts/kor/values.yaml --- old/kor-0.3.1/charts/kor/values.yaml 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/charts/kor/values.yaml 2023-12-09 22:04:13.000000000 +0100 @@ -6,7 +6,10 @@ repository: yonahdissen/kor tag: latest # e.g. kor configmap --include-namespace default,other-ns - command: kor all + command: + - kor + args: + - all slackWebhookUrl: "" slackChannel: "" slackAuthToken: "" @@ -19,11 +22,14 @@ name: kor-exporter # time in minutes, default is 10 minutes exporterInterval: "" + command: + - kor + args: + - exporter deployment: image: repository: yonahdissen/kor tag: latest - command: kor exporter restartPolicy: Always imagePullPolicy: Always imagePullSecrets: [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/cmd/kor/jobs.go new/kor-0.3.2/cmd/kor/jobs.go --- old/kor-0.3.1/cmd/kor/jobs.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kor-0.3.2/cmd/kor/jobs.go 2023-12-09 22:04:13.000000000 +0100 @@ -0,0 +1,29 @@ +package kor + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/yonahd/kor/pkg/kor" + "github.com/yonahd/kor/pkg/utils" +) + +var jobCmd = &cobra.Command{ + Use: "job", + Aliases: []string{"job", "jobs"}, + Short: "Gets unused jobs", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + clientset := kor.GetKubeClient(kubeconfig) + if response, err := kor.GetUnusedJobs(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil { + fmt.Println(err) + } else { + utils.PrintLogo(outputFormat) + fmt.Println(response) + } + }, +} + +func init() { + rootCmd.AddCommand(jobCmd) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/cmd/kor/pods.go new/kor-0.3.2/cmd/kor/pods.go --- old/kor-0.3.1/cmd/kor/pods.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kor-0.3.2/cmd/kor/pods.go 2023-12-09 22:04:13.000000000 +0100 @@ -0,0 +1,29 @@ +package kor + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/yonahd/kor/pkg/kor" + "github.com/yonahd/kor/pkg/utils" +) + +var podCmd = &cobra.Command{ + Use: "pod", + Aliases: []string{"po", "pods"}, + Short: "Gets unused pods", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + clientset := kor.GetKubeClient(kubeconfig) + if response, err := kor.GetUnusedPods(includeExcludeLists, filterOptions, clientset, outputFormat, opts); err != nil { + fmt.Println(err) + } else { + utils.PrintLogo(outputFormat) + fmt.Println(response) + } + }, +} + +func init() { + rootCmd.AddCommand(podCmd) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/all.go new/kor-0.3.2/pkg/kor/all.go --- old/kor-0.3.1/pkg/kor/all.go 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/all.go 2023-12-09 22:04:13.000000000 +0100 @@ -138,6 +138,24 @@ return allPvDiff } +func getUnusedPods(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff { + podDiff, err := ProcessNamespacePods(clientset, namespace, filterOpts) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "pods", namespace, err) + } + namespacePodDiff := ResourceDiff{"Pod", podDiff} + return namespacePodDiff +} + +func getUnusedJobs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ResourceDiff { + jobDiff, err := ProcessNamespaceJobs(clientset, namespace, filterOpts) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get %s namespace %s: %v\n", "jobs", namespace, err) + } + namespaceSADiff := ResourceDiff{"Job", jobDiff} + return namespaceSADiff +} + func GetUnusedAll(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, apiExtClient apiextensionsclientset.Interface, dynamicClient dynamic.Interface, outputFormat string, opts Opts) (string, error) { var outputBuffer bytes.Buffer @@ -168,6 +186,8 @@ allDiffs = append(allDiffs, namespaceIngressDiff) namespacePdbDiff := getUnusedPdbs(clientset, namespace, filterOpts) allDiffs = append(allDiffs, namespacePdbDiff) + namespaceJobDiff := getUnusedJobs(clientset, namespace, filterOpts) + allDiffs = append(allDiffs, namespaceJobDiff) output := FormatOutputAll(namespace, allDiffs, opts) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/create_test_resources.go new/kor-0.3.2/pkg/kor/create_test_resources.go --- old/kor-0.3.1/pkg/kor/create_test_resources.go 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/create_test_resources.go 2023-12-09 22:04:13.000000000 +0100 @@ -3,6 +3,7 @@ import ( appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" policyv1 "k8s.io/api/policy/v1" @@ -259,3 +260,26 @@ }, } } + +func CreateTestJob(namespace, name string, status *batchv1.JobStatus) *batchv1.Job { + return &batchv1.Job{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test", + Image: "test", + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + }, + }, + Status: *status, + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/delete.go new/kor-0.3.2/pkg/kor/delete.go --- old/kor-0.3.1/pkg/kor/delete.go 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/delete.go 2023-12-09 22:04:13.000000000 +0100 @@ -7,6 +7,8 @@ "reflect" "strings" + batchv1 "k8s.io/api/batch/v1" + appsv1 "k8s.io/api/apps/v1" autoscalingv1 "k8s.io/api/autoscaling/v1" corev1 "k8s.io/api/core/v1" @@ -55,6 +57,12 @@ "PV": func(clientset kubernetes.Interface, namespace, name string) error { return clientset.CoreV1().PersistentVolumes().Delete(context.TODO(), name, metav1.DeleteOptions{}) }, + "Pod": func(clientset kubernetes.Interface, namespace, name string) error { + return clientset.CoreV1().Pods(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + }, + "Job": func(clientset kubernetes.Interface, namespace, name string) error { + return clientset.BatchV1().Jobs(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + }, } return deleteResourceApiMap @@ -108,6 +116,10 @@ return clientset.CoreV1().ServiceAccounts(namespace).Update(context.TODO(), resource.(*corev1.ServiceAccount), metav1.UpdateOptions{}) case "PV": return clientset.CoreV1().PersistentVolumes().Update(context.TODO(), resource.(*corev1.PersistentVolume), metav1.UpdateOptions{}) + case "Pod": + return clientset.CoreV1().Pods(namespace).Update(context.TODO(), resource.(*corev1.Pod), metav1.UpdateOptions{}) + case "Job": + return clientset.BatchV1().Jobs(namespace).Update(context.TODO(), resource.(*batchv1.Job), metav1.UpdateOptions{}) } return nil, fmt.Errorf("resource type '%s' is not supported", resourceType) } @@ -138,6 +150,10 @@ return clientset.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) case "PV": return clientset.CoreV1().PersistentVolumes().Get(context.TODO(), resourceName, metav1.GetOptions{}) + case "Pod": + return clientset.CoreV1().Pods(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + case "Job": + return clientset.BatchV1().Jobs(namespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) } return nil, fmt.Errorf("resource type '%s' is not supported", resourceType) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/jobs.go new/kor-0.3.2/pkg/kor/jobs.go --- old/kor-0.3.1/pkg/kor/jobs.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/jobs.go 2023-12-09 22:04:13.000000000 +0100 @@ -0,0 +1,85 @@ +package kor + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "os" +) + +func ProcessNamespaceJobs(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) { + jobsList, err := clientset.BatchV1().Jobs(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + + var unusedJobNames []string + + for _, job := range jobsList.Items { + if job.Labels["kor/used"] == "true" { + continue + } + + // checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels. + // If it does, the resource is skipped. + if excluded, _ := HasExcludedLabel(job.Labels, filterOpts.ExcludeLabels); excluded { + continue + } + // checks if the resource's age (measured from its last modified time) matches the included criteria + // specified by the filter options. + if included, _ := HasIncludedAge(job.CreationTimestamp, filterOpts); !included { + continue + } + + // if the job has completionTime and succeeded count greater than zero, think the job is completed + if job.Status.CompletionTime != nil && job.Status.Succeeded > 0 { + unusedJobNames = append(unusedJobNames, job.Name) + } + } + + return unusedJobNames, nil +} + +func GetUnusedJobs(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) { + var outputBuffer bytes.Buffer + namespaces := SetNamespaceList(includeExcludeLists, clientset) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := ProcessNamespaceJobs(clientset, namespace, filterOpts) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + + if opts.DeleteFlag { + if diff, err = DeleteResource(diff, clientset, namespace, "Job", opts.NoInteractive); err != nil { + fmt.Fprintf(os.Stderr, "Failed to delete Job %s in namespace %s: %v\n", diff, namespace, err) + } + } + output := FormatOutput(namespace, diff, "Job", opts) + if output != "" { + outputBuffer.WriteString(output) + outputBuffer.WriteString("\n") + + resourceMap := make(map[string][]string) + resourceMap["Jobs"] = diff + response[namespace] = resourceMap + } + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + unusedJobs, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse) + if err != nil { + fmt.Printf("err: %v\n", err) + } + + return unusedJobs, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/jobs_test.go new/kor-0.3.2/pkg/kor/jobs_test.go --- old/kor-0.3.1/pkg/kor/jobs_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/jobs_test.go 2023-12-09 22:04:13.000000000 +0100 @@ -0,0 +1,108 @@ +package kor + +import ( + "context" + "encoding/json" + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + "reflect" + "testing" + "time" +) + +func createTestJobs(t *testing.T) *fake.Clientset { + clientset := fake.NewSimpleClientset() + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{Name: testNamespace}, + }, v1.CreateOptions{}) + + if err != nil { + t.Fatalf("Error creating namespace %s: %v", testNamespace, err) + } + + job1 := CreateTestJob(testNamespace, "test-job1", &batchv1.JobStatus{ + Succeeded: 0, + Failed: 1, + }) + job2 := CreateTestJob(testNamespace, "test-job2", &batchv1.JobStatus{ + Succeeded: 1, + Failed: 0, + CompletionTime: &v1.Time{Time: time.Now()}, + }) + + _, err = clientset.BatchV1().Jobs(testNamespace).Create(context.TODO(), job1, v1.CreateOptions{}) + if err != nil { + t.Fatalf("Error creating fake job: %v", err) + } + + _, err = clientset.BatchV1().Jobs(testNamespace).Create(context.TODO(), job2, v1.CreateOptions{}) + if err != nil { + t.Fatalf("Error creating fake job: %v", err) + } + return clientset +} + +func TestProcessNamespaceJobs(t *testing.T) { + clientset := createTestJobs(t) + + completedJobs, err := ProcessNamespaceJobs(clientset, testNamespace, &FilterOptions{}) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if len(completedJobs) != 1 { + t.Errorf("Expected 1 job been completed, got %d", len(completedJobs)) + } + + if completedJobs[0] != "test-job2" { + t.Errorf("job2', got %s", completedJobs[0]) + } +} + +func TestGetUnusedJobsStructured(t *testing.T) { + clientset := createTestJobs(t) + + includeExcludeLists := IncludeExcludeLists{ + IncludeListStr: "", + ExcludeListStr: "", + } + + opts := Opts{ + WebhookURL: "", + Channel: "", + Token: "", + DeleteFlag: false, + NoInteractive: true, + } + + output, err := GetUnusedJobs(includeExcludeLists, &FilterOptions{}, clientset, "json", opts) + if err != nil { + t.Fatalf("Error calling GetUnusedJobsStructured: %v", err) + } + + expectedOutput := map[string]map[string][]string{ + testNamespace: { + "Jobs": {"test-job2"}, + }, + } + + var actualOutput map[string]map[string][]string + if err := json.Unmarshal([]byte(output), &actualOutput); err != nil { + t.Fatalf("Error unmarshaling actual output: %v", err) + } + + if !reflect.DeepEqual(expectedOutput, actualOutput) { + t.Errorf("Expected output does not match actual output") + } +} + +func init() { + scheme.Scheme = runtime.NewScheme() + _ = appsv1.AddToScheme(scheme.Scheme) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/multi.go new/kor-0.3.2/pkg/kor/multi.go --- old/kor-0.3.1/pkg/kor/multi.go 2023-11-29 19:52:39.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/multi.go 2023-12-09 22:04:13.000000000 +0100 @@ -68,6 +68,10 @@ diffResult = getUnusedIngresses(clientset, namespace, filterOpts) case "pdb", "poddisruptionbudget", "poddisruptionbudgets": diffResult = getUnusedPdbs(clientset, namespace, filterOpts) + case "po", "pod", "pods": + diffResult = getUnusedPods(clientset, namespace, filterOpts) + case "job", "jobs": + diffResult = getUnusedJobs(clientset, namespace, filterOpts) default: fmt.Printf("resource type %q is not supported\n", resource) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/pods.go new/kor-0.3.2/pkg/kor/pods.go --- old/kor-0.3.1/pkg/kor/pods.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/pods.go 2023-12-09 22:04:13.000000000 +0100 @@ -0,0 +1,87 @@ +package kor + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +func ProcessNamespacePods(clientset kubernetes.Interface, namespace string, filterOpts *FilterOptions) ([]string, error) { + podsList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + + var evictedPods []string + + for _, pod := range podsList.Items { + if pod.Labels["kor/used"] == "true" { + continue + } + + // checks if the resource has any labels that match the excluded selector specified in opts.ExcludeLabels. + // If it does, the resource is skipped. + if excluded, _ := HasExcludedLabel(pod.Labels, filterOpts.ExcludeLabels); excluded { + continue + } + // checks if the resource's age (measured from its last modified time) matches the included criteria + // specified by the filter options. + if included, _ := HasIncludedAge(pod.CreationTimestamp, filterOpts); !included { + continue + } + + if pod.Status.Phase == corev1.PodFailed && pod.Status.Reason == "Evicted" { + evictedPods = append(evictedPods, pod.Name) + } + + } + + return evictedPods, nil +} + +func GetUnusedPods(includeExcludeLists IncludeExcludeLists, filterOpts *FilterOptions, clientset kubernetes.Interface, outputFormat string, opts Opts) (string, error) { + var outputBuffer bytes.Buffer + namespaces := SetNamespaceList(includeExcludeLists, clientset) + response := make(map[string]map[string][]string) + + for _, namespace := range namespaces { + diff, err := ProcessNamespacePods(clientset, namespace, filterOpts) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to process namespace %s: %v\n", namespace, err) + continue + } + + if opts.DeleteFlag { + if diff, err = DeleteResource(diff, clientset, namespace, "Pod", opts.NoInteractive); err != nil { + fmt.Fprintf(os.Stderr, "Failed to delete Pod %s in namespace %s: %v\n", diff, namespace, err) + } + } + output := FormatOutput(namespace, diff, "Pods", opts) + if output != "" { + outputBuffer.WriteString(output) + outputBuffer.WriteString("\n") + + resourceMap := make(map[string][]string) + resourceMap["Pods"] = diff + response[namespace] = resourceMap + } + } + + jsonResponse, err := json.MarshalIndent(response, "", " ") + if err != nil { + return "", err + } + + unusedPods, err := unusedResourceFormatter(outputFormat, outputBuffer, opts, jsonResponse) + if err != nil { + fmt.Printf("err: %v\n", err) + } + + return unusedPods, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.3.1/pkg/kor/pods_test.go new/kor-0.3.2/pkg/kor/pods_test.go --- old/kor-0.3.1/pkg/kor/pods_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kor-0.3.2/pkg/kor/pods_test.go 2023-12-09 22:04:13.000000000 +0100 @@ -0,0 +1,146 @@ +package kor + +import ( + "context" + "encoding/json" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + fake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" +) + +func createTestPods(t *testing.T) *fake.Clientset { + clientset := fake.NewSimpleClientset() + + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ + ObjectMeta: v1.ObjectMeta{Name: testNamespace}, + }, v1.CreateOptions{}) + + if err != nil { + t.Fatalf("Error creating namespace %s: %v", testNamespace, err) + } + + pod1 := CreateTestPod(testNamespace, "pod-1", "", nil) + pod1.Status = corev1.PodStatus{ + Phase: corev1.PodRunning, + Reason: "", + Message: "", + } + pod2 := CreateTestPod(testNamespace, "pod-2", "", nil) + pod2.Status = corev1.PodStatus{ + Phase: corev1.PodFailed, + Reason: "Evicted", + Message: "", + } + + pod3 := CreateTestPod(testNamespace, "pod-3", "", nil) + pod3.Status = corev1.PodStatus{ + Phase: corev1.PodFailed, + Reason: "CrashLoopBackOff", + Message: "", + } + + pod4 := CreateTestPod(testNamespace, "pod-4", "", nil) + pod4.Status = corev1.PodStatus{ + Phase: corev1.PodSucceeded, + Reason: "", + Message: "", + } + + pod5 := CreateTestPod(testNamespace, "pod-5", "", nil) + pod5.Labels = map[string]string{"kor/used": "true"} + pod5.Status = corev1.PodStatus{ + Phase: corev1.PodFailed, + Reason: "Evicted", + Message: "", + } + + pod6 := CreateTestPod(testNamespace, "pod-6", "", nil) + pod6.Labels = map[string]string{"kor/used": "false"} + pod6.Status = corev1.PodStatus{ + Phase: corev1.PodFailed, + Reason: "Evicted", + Message: "", + } + + pods := []*corev1.Pod{pod1, pod2, pod3, pod4, pod5, pod6} + + // Add test pods to the clientset + for _, pod := range pods { + _, err = clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, v1.CreateOptions{}) + if err != nil { + t.Fatalf("Error creating fake pod: %v", err) + } + } + + return clientset +} + +func TestProcessNamespacePods(t *testing.T) { + clientset := createTestPods(t) + evictedPods, err := ProcessNamespacePods(clientset, testNamespace, &FilterOptions{}) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expectedEvictedPods := []string{"pod-2", "pod-6"} + + if len(evictedPods) != len(expectedEvictedPods) { + t.Errorf("Expected %d evicted pods, got %d", len(expectedEvictedPods), len(evictedPods)) + } + + for i, pod := range evictedPods { + if pod != expectedEvictedPods[i] { + t.Errorf("Expected evicted pod %s, got %s", expectedEvictedPods[i], pod) + } + } +} + +func TestGetUnusedPodsStructured(t *testing.T) { + clientset := createTestPods(t) + + includeExcludeLists := IncludeExcludeLists{ + IncludeListStr: "", + ExcludeListStr: "", + } + + opts := Opts{ + WebhookURL: "", + Channel: "", + Token: "", + DeleteFlag: false, + NoInteractive: true, + } + + output, err := GetUnusedPods(includeExcludeLists, &FilterOptions{}, clientset, "json", opts) + if err != nil { + t.Fatalf("Error calling GetUnusedPodsStructured: %v", err) + } + + expectedOutput := map[string]map[string][]string{ + testNamespace: { + "Pods": {"pod-2", "pod-6"}, + }, + } + + var actualOutput map[string]map[string][]string + if err := json.Unmarshal([]byte(output), &actualOutput); err != nil { + t.Fatalf("Error unmarshaling actual output: %v", err) + } + + if !reflect.DeepEqual(expectedOutput, actualOutput) { + t.Errorf("Expected output does not match actual output") + t.Errorf("Expected: %v", expectedOutput) + t.Errorf("Actual: %v", actualOutput) + } +} + +func init() { + scheme.Scheme = runtime.NewScheme() + _ = corev1.AddToScheme(scheme.Scheme) +} ++++++ kor.obsinfo ++++++ --- /var/tmp/diff_new_pack.ORjqk5/_old 2023-12-11 21:50:42.767886785 +0100 +++ /var/tmp/diff_new_pack.ORjqk5/_new 2023-12-11 21:50:42.771886934 +0100 @@ -1,5 +1,5 @@ name: kor -version: 0.3.1 -mtime: 1701283959 -commit: a459be1a0dfcf5d96ab0ed8c86ec8dc2d675de39 +version: 0.3.2 +mtime: 1702155853 +commit: d5ad46865819f405591a7c37f34b631f8d756c89 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kor/vendor.tar.gz /work/SRC/openSUSE:Factory/.kor.new.25432/vendor.tar.gz differ: char 5, line 1
