diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cfe09a0b..7e848c45 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for additional information on contributing to this repository! + -## What this PR does / why we need it +## What this PR does / why we need it? ## PR Checklist - [ ] This PR adds K8s exceptions (false positives) - [ ] This PR adds new code -- [ ] This PR includes test for any new code +- [ ] This PR includes tests for new/existing code +- [ ] This PR adds docs + -## Github Issue +## GitHub Issue [XX-XX] diff --git a/README.md b/README.md index 0ffa09f3..57010692 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ kor [subcommand] --help | CRDs | CRDs not used the cluster | | | Pvs | PVs not bound to a PVC | | | Pdbs | PDBs not used in Deployments
PDBs not used in StatefulSets | | -| Jobs | Jobs status is completed | | +| Jobs | Jobs status is completed
Jobs failed with no retries left | | | ReplicaSets | replicaSets that specify replicas to 0 and has already completed it's work | | DaemonSets | DaemonSets not scheduled on any nodes | | StorageClasses | StorageClasses not used by any PVs/PVCs | diff --git a/charts/kor/Chart.yaml b/charts/kor/Chart.yaml index e1cb7918..0e91b296 100644 --- a/charts/kor/Chart.yaml +++ b/charts/kor/Chart.yaml @@ -1,24 +1,6 @@ apiVersion: v2 name: kor -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. +description: A Kubernetes Helm Chart to discover orphaned resources using kor type: application - -# 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.5 - -# 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. +version: 0.1.6 appVersion: "0.4.0" diff --git a/charts/kor/README.md b/charts/kor/README.md index fb01af6a..8808b803 100644 --- a/charts/kor/README.md +++ b/charts/kor/README.md @@ -1,8 +1,8 @@ # kor -![Version: 0.1.5](https://img.shields.io/badge/Version-0.1.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.4.0](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) +![Version: 0.1.6](https://img.shields.io/badge/Version-0.1.6-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.4.0](https://img.shields.io/badge/AppVersion-0.4.0-informational?style=flat-square) -A Helm chart for Kubernetes +A Kubernetes Helm Chart to discover orphaned resources using kor ## Values @@ -39,21 +39,20 @@ A Helm chart for Kubernetes | prometheusExporter.enabled | bool | `true` | | | prometheusExporter.exporterInterval | string | `""` | | | prometheusExporter.name | string | `"kor-exporter"` | | -| prometheusExporter.service.enabled | bool | `true` | | -| prometheusExporter.service.interval | string | `"30s"` | | -| prometheusExporter.service.labels | object | `{}` | | -| prometheusExporter.service.metricRelabelings | list | `[]` | | -| prometheusExporter.service.namespace | string | `""` | | | prometheusExporter.service.port | int | `8080` | | -| prometheusExporter.service.relabelings | list | `[]` | | -| prometheusExporter.service.serviceMonitor | string | `nil` | | -| prometheusExporter.service.targetLabels | list | `[]` | | -| prometheusExporter.service.telemetryPath | string | `"/metrics"` | | -| prometheusExporter.service.timeout | string | `"10s"` | | | prometheusExporter.service.type | string | `"ClusterIP"` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | +| prometheusExporter.serviceMonitor.enabled | bool | `true` | | +| prometheusExporter.serviceMonitor.interval | string | `"30s"` | Set how frequently Prometheus should scrape | +| prometheusExporter.serviceMonitor.labels | object | `{}` | Service monitor labels | +| prometheusExporter.serviceMonitor.metricRelabelings | list | `[]` | | +| prometheusExporter.serviceMonitor.namespace | string | `""` | Set the namespace the ServiceMonitor should be deployed, if empty namespace will be `.Release.Namespace` | +| prometheusExporter.serviceMonitor.relabelings | list | `[]` | | +| prometheusExporter.serviceMonitor.targetLabels | list | `[]` | Set of labels to transfer on the Kubernetes Service onto the target. | +| prometheusExporter.serviceMonitor.telemetryPath | string | `"/metrics"` | | +| prometheusExporter.serviceMonitor.timeout | string | `"10s"` | Set timeout for scrape | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) diff --git a/charts/kor/values.yaml b/charts/kor/values.yaml index 9ccd72a9..4a251c0b 100644 --- a/charts/kor/values.yaml +++ b/charts/kor/values.yaml @@ -61,28 +61,28 @@ prometheusExporter: serviceMonitor: enabled: true - # Set the namespace the ServiceMonitor should be deployed, if empty namespace will be .Release.Namespace + # -- Set the namespace the ServiceMonitor should be deployed, if empty namespace will be `.Release.Namespace` namespace: "" - # Service monitor labels + # -- Service monitor labels labels: {} - # Set how frequently Prometheus should scrape + # -- Set how frequently Prometheus should scrape interval: 30s telemetryPath: /metrics - # Set timeout for scrape + # -- Set timeout for scrape timeout: 10s relabelings: [] - # Set of labels to transfer on the Kubernetes Service onto the target. + # -- Set of labels to transfer on the Kubernetes Service onto the target. targetLabels: [] metricRelabelings: [] -# Custom labels to add into metadata +# -- Custom labels to add into metadata additionalLabels: {} serviceAccount: - # Specifies whether a service account should be created + # -- Specifies whether a service account should be created create: true - # Annotations to add to the service account + # -- Annotations to add to the service account annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # -- The name of the service account to use. + # -- If not set and create is true, a name is generated using the fullname template name: "" diff --git a/kor b/kor new file mode 100755 index 00000000..5db9950d Binary files /dev/null and b/kor differ diff --git a/pkg/kor/configmaps_test.go b/pkg/kor/configmaps_test.go index e9d33ed8..065c4f01 100644 --- a/pkg/kor/configmaps_test.go +++ b/pkg/kor/configmaps_test.go @@ -168,7 +168,9 @@ func TestRetrieveUsedCM(t *testing.T) { expectedVolumesCM := []string{ "configmap-1", "kube-root-ca.crt", + "openshift-service-ca.crt", } + if !equalSlices(volumesCM, expectedVolumesCM) { t.Errorf("Expected volume configmaps %v, got %v", expectedVolumesCM, volumesCM) } diff --git a/pkg/kor/exceptions/clusterroles/clusterroles.json b/pkg/kor/exceptions/clusterroles/clusterroles.json index e01578bc..bcf7fd59 100644 --- a/pkg/kor/exceptions/clusterroles/clusterroles.json +++ b/pkg/kor/exceptions/clusterroles/clusterroles.json @@ -83,6 +83,186 @@ { "Namespace": "", "ResourceName": "view" + }, + { + "Namespace": "", + "ResourceName": "system:node-bootstrapper" + }, + { + "Namespace": "", + "ResourceName": "system:aggregated-metrics-reader" + }, + { + "Namespace": "", + "ResourceName": "alert-routing-edit" + }, + { + "Namespace": "", + "ResourceName": "cluster-debugger" + }, + { + "Namespace": "", + "ResourceName": "global-operators-admin" + }, + { + "Namespace": "", + "ResourceName": "global-operators-edit" + }, + { + "Namespace": "", + "ResourceName": "global-operators-view" + }, + { + "Namespace": "", + "ResourceName": "monitoring-edit" + }, + { + "Namespace": "", + "ResourceName": "monitoring-rules-edit" + }, + { + "Namespace": "", + "ResourceName": "monitoring-rules-view" + }, + { + "Namespace": "", + "ResourceName": "olm-operators-admin" + }, + { + "Namespace": "", + "ResourceName": "olm-operators-edit" + }, + { + "Namespace": "", + "ResourceName": "olm-operators-view" + }, + { + "Namespace": "", + "ResourceName": "openshift-cluster-monitoring-admin" + }, + { + "Namespace": "", + "ResourceName": "openshift-cluster-monitoring-edit" + }, + { + "Namespace": "", + "ResourceName": "openshift-cluster-monitoring-view" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-main-attacher-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-main-provisioner-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-main-resizer-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-main-snapshotter-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-provisioner-configmap-and-secret-reader-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-provisioner-volumeattachment-reader-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-provisioner-volumesnapshot-reader-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-resizer-infrastructure-reader-role" + }, + { + "Namespace": "", + "ResourceName": "openshift-csi-resizer-storageclass-reader-role" + }, + { + "Namespace": "", + "ResourceName": "resource-metrics-server-resources" + }, + { + "Namespace": "", + "ResourceName": "storage-admin" + }, + { + "Namespace": "", + "ResourceName": "sudoer" + }, + { + "Namespace": "", + "ResourceName": "system:build-strategy-custom" + }, + { + "Namespace": "", + "ResourceName": "system:image-auditor" + }, + { + "Namespace": "", + "ResourceName": "system:image-pusher" + }, + { + "Namespace": "", + "ResourceName": "system:image-signer" + }, + { + "Namespace": "", + "ResourceName": "system:node-reader" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:aggregate-snapshots-to-storage-admin" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:aggregate-to-storage-admin" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:hostaccess" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:hostmount" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:hostnetwork" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:nonroot" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:nonroot-v2" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:privileged" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:scc:restricted" + }, + { + "Namespace": "", + "ResourceName": "system:openshift:templateservicebroker-client" + }, + { + "Namespace": "", + "ResourceName": "system:router" + }, + { + "Namespace": "", + "ResourceName": "system:sdn-manager" } ] } diff --git a/pkg/kor/exceptions/configmaps/configmaps.json b/pkg/kor/exceptions/configmaps/configmaps.json index 81d9cd5f..89d0b1c0 100644 --- a/pkg/kor/exceptions/configmaps/configmaps.json +++ b/pkg/kor/exceptions/configmaps/configmaps.json @@ -4,6 +4,10 @@ "Namespace": "*", "ResourceName": "kube-root-ca.crt" }, + { + "Namespace": "*", + "ResourceName": "openshift-service-ca.crt" + }, { "Namespace": "gmp-system", "ResourceName": "config-images" @@ -76,6 +80,22 @@ "Namespace": "kube-system", "ResourceName": "overlay-upgrade-data" }, + { + "Namespace": "kube-system", + "ResourceName": "cluster-dns" + }, + { + "Namespace": "kube-system", + "ResourceName": "bootstrap" + }, + { + "Namespace": "kube-system", + "ResourceName": "cluster-config-v1" + }, + { + "Namespace": "kube-system", + "ResourceName": "root-ca" + }, { "Namespace": "kubernetes-dashboard", "ResourceName": "kubernetes-dashboard-settings" diff --git a/pkg/kor/exceptions/crds/crds.json b/pkg/kor/exceptions/crds/crds.json index 6bac7012..9f176ab5 100644 --- a/pkg/kor/exceptions/crds/crds.json +++ b/pkg/kor/exceptions/crds/crds.json @@ -83,6 +83,286 @@ { "Namespace": "", "ResourceName": "volumesnapshots.snapshot.storage.k8s.io" + }, + { + "Namespace": "", + "ResourceName": "etcdsnapshotfiles.k3s.cattle.io" + }, + { + "Namespace": "", + "ResourceName": "helmchartconfigs.helm.cattle.io" + }, + { + "Namespace": "", + "ResourceName": "ingressrouteudps.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "ingressrouteudps.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "middlewaretcps.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "ingressroutetcps.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "serverstransports.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "middlewares.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "serverstransports.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "tlsstores.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "traefikservices.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "tlsstores.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "tlsoptions.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "traefikservices.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "serverstransporttcps.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "middlewaretcps.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "ingressroutes.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "tlsoptions.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "ingressroutetcps.traefik.containo.us" + }, + { + "Namespace": "", + "ResourceName": "middlewares.traefik.io" + }, + { + "Namespace": "", + "ResourceName": "adminpolicybasedexternalroutes.k8s.ovn.org" + }, + { + "Namespace": "", + "ResourceName": "alertingrules.monitoring.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "alertmanagerconfigs.monitoring.coreos.com" + }, + { + "Namespace": "", + "ResourceName": "alertrelabelconfigs.monitoring.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "baremetalhosts.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "bmceventsubscriptions.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "clusterautoscalers.autoscaling.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "clustercsidrivers.operator.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "clusterresourcequotas.quota.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "consoleexternalloglinks.console.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "consolelinks.console.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "consolenotifications.console.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "consolesamples.console.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "consoleyamlsamples.console.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "containerruntimeconfigs.machineconfiguration.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "controlplanemachinesets.machine.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "dnsrecords.ingress.operator.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "egressfirewalls.k8s.ovn.org" + }, + { + "Namespace": "", + "ResourceName": "egressips.k8s.ovn.org" + }, + { + "Namespace": "", + "ResourceName": "egressqoses.k8s.ovn.org" + }, + { + "Namespace": "", + "ResourceName": "egressrouters.network.operator.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "egressservices.k8s.ovn.org" + }, + { + "Namespace": "", + "ResourceName": "firmwareschemas.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "hardwaredata.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "hostfirmwaresettings.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "imagecontentpolicies.config.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "imagecontentsourcepolicies.operator.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "imagedigestmirrorsets.config.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "imagetagmirrorsets.config.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "installplans.operators.coreos.com" + }, + { + "Namespace": "", + "ResourceName": "ippools.whereabouts.cni.cncf.io" + }, + { + "Namespace": "", + "ResourceName": "kubeletconfigs.machineconfiguration.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "machineautoscalers.autoscaling.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "machines.machine.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "machinesets.machine.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "metal3remediations.infrastructure.cluster.x-k8s.io" + }, + { + "Namespace": "", + "ResourceName": "metal3remediationtemplates.infrastructure.cluster.x-k8s.io" + }, + { + "Namespace": "", + "ResourceName": "network-attachment-definitions.k8s.cni.cncf.io" + }, + { + "Namespace": "", + "ResourceName": "operators.operators.coreos.com" + }, + { + "Namespace": "", + "ResourceName": "overlappingrangeipreservations.whereabouts.cni.cncf.io" + }, + { + "Namespace": "", + "ResourceName": "performanceprofiles.performance.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "podmonitors.monitoring.coreos.com" + }, + { + "Namespace": "", + "ResourceName": "preprovisioningimages.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "probes.monitoring.coreos.com" + }, + { + "Namespace": "", + "ResourceName": "projecthelmchartrepositories.helm.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "provisionings.metal3.io" + }, + { + "Namespace": "", + "ResourceName": "rolebindingrestrictions.authorization.openshift.io" + }, + { + "Namespace": "", + "ResourceName": "storagestates.migration.k8s.io" + }, + { + "Namespace": "", + "ResourceName": "subscriptions.operators.coreos.com" + }, + { + "Namespace": "", + "ResourceName": "thanosrulers.monitoring.coreos.com" } ] } diff --git a/pkg/kor/exceptions/jobs/jobs.json b/pkg/kor/exceptions/jobs/jobs.json new file mode 100644 index 00000000..af4155e6 --- /dev/null +++ b/pkg/kor/exceptions/jobs/jobs.json @@ -0,0 +1,16 @@ +{ + "exceptionJobs": [ + { + "Namespace": "kube-system", + "ResourceName": "helm-install-traefik-crd" + }, + { + "Namespace": "kube-system", + "ResourceName": "helm-install-traefik" + }, + { + "Namespace": "assisted-installer", + "ResourceName": "assisted-installer-controller" + } + ] + } diff --git a/pkg/kor/exceptions/secrets/secrets.json b/pkg/kor/exceptions/secrets/secrets.json index a91be3fb..63a08ca1 100644 --- a/pkg/kor/exceptions/secrets/secrets.json +++ b/pkg/kor/exceptions/secrets/secrets.json @@ -4,6 +4,22 @@ "Namespace": "kube-system", "ResourceName": "bootstrap-token-*" }, + { + "Namespace": "kube-system", + "ResourceName": "k3s-serving" + }, + { + "Namespace": "kube-system", + "ResourceName": "*.node-password.k3s" + }, + { + "Namespace": "kube-system", + "ResourceName": "kube-cloud-cfg" + }, + { + "Namespace": "kube-system", + "ResourceName": "kubeadmin" + }, { "Namespace": "kubernetes-dashboard", "ResourceName": "kubernetes-dashboard-certs" diff --git a/pkg/kor/exceptions/storageclasses/storageclasses.json b/pkg/kor/exceptions/storageclasses/storageclasses.json index 6c1d97a8..7e2dd028 100644 --- a/pkg/kor/exceptions/storageclasses/storageclasses.json +++ b/pkg/kor/exceptions/storageclasses/storageclasses.json @@ -43,6 +43,10 @@ { "Namespace": "", "ResourceName": "standard-rwo" + }, + { + "Namespace": "", + "ResourceName": "local-path" } ] } diff --git a/pkg/kor/finalizers.go b/pkg/kor/finalizers.go index 368ea992..a01a4bf7 100644 --- a/pkg/kor/finalizers.go +++ b/pkg/kor/finalizers.go @@ -99,7 +99,6 @@ func GetUnusedfinalizers(filterOpts *filters.Options, clientset kubernetes.Inter output := formatOutputForNamespace(namespace, allDiffs, opts) outputBuffer.WriteString(output) - outputBuffer.WriteString("\n") response[namespace] = allDiffs } diff --git a/pkg/kor/jobs.go b/pkg/kor/jobs.go index c9ee3eba..3fb4cb4c 100644 --- a/pkg/kor/jobs.go +++ b/pkg/kor/jobs.go @@ -3,22 +3,32 @@ package kor import ( "bytes" "context" + _ "embed" "encoding/json" "fmt" "os" + batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "github.com/yonahd/kor/pkg/filters" ) +//go:embed exceptions/jobs/jobs.json +var jobsConfig []byte + func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filterOpts *filters.Options) ([]string, error) { jobsList, err := clientset.BatchV1().Jobs(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: filterOpts.IncludeLabels}) if err != nil { return nil, err } + config, err := unmarshalConfig(jobsConfig) + if err != nil { + return nil, err + } + var unusedJobNames []string for _, job := range jobsList.Items { @@ -26,9 +36,22 @@ func processNamespaceJobs(clientset kubernetes.Interface, namespace string, filt continue } + if isResourceException(job.Name, job.Namespace, config.ExceptionJobs) { + 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) + continue + } else { + // Check if the job has a condition indicating it has exceeded the backoff limit + for _, condition := range job.Status.Conditions { + if condition.Type == batchv1.JobFailed && condition.Reason == "BackoffLimitExceeded" { + unusedJobNames = append(unusedJobNames, job.Name) + break + } + } } } diff --git a/pkg/kor/jobs_test.go b/pkg/kor/jobs_test.go index 422fd533..6fe0235b 100644 --- a/pkg/kor/jobs_test.go +++ b/pkg/kor/jobs_test.go @@ -71,23 +71,44 @@ func createTestJobs(t *testing.T) *fake.Clientset { t.Fatalf("Error creating fake job: %v", err) } + job5 := CreateTestJob(testNamespace, "test-job5", &batchv1.JobStatus{ + Succeeded: 0, + Failed: 1, + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobFailed, + Status: corev1.ConditionTrue, + Reason: "BackoffLimitExceeded", + Message: "Job has reached the specified backoff limit", + }, + }, + }, AppLabels) + + _, err = clientset.BatchV1().Jobs(testNamespace).Create(context.TODO(), job5, 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, &filters.Options{}) + unusedJobs, err := processNamespaceJobs(clientset, testNamespace, &filters.Options{}) if err != nil { t.Errorf("Expected no error, got %v", err) } - if len(completedJobs) != 2 { - t.Errorf("Expected 2 job been completed, got %d", len(completedJobs)) + if len(unusedJobs) != 3 { + t.Errorf("Expected 3 jobs unused got %d", len(unusedJobs)) } - if completedJobs[0] != "test-job2" && completedJobs[1] != "test-job4" { - t.Errorf("job2', got %s", completedJobs[0]) + expectedJobs := []string{"test-job2", "test-job4", "test-job5"} + for _, jobName := range expectedJobs { + if !contains(unusedJobs, jobName) { + t.Errorf("Expected job %s to be considered unused, but it was not found", jobName) + } } } @@ -113,6 +134,7 @@ func TestGetUnusedJobsStructured(t *testing.T) { "Job": { "test-job2", "test-job4", + "test-job5", }, }, } diff --git a/pkg/kor/kor.go b/pkg/kor/kor.go index 21713548..650d9c83 100644 --- a/pkg/kor/kor.go +++ b/pkg/kor/kor.go @@ -38,6 +38,7 @@ type Config struct { ExceptionServiceAccounts []ExceptionResource `json:"exceptionServiceAccounts"` ExceptionServices []ExceptionResource `json:"exceptionServices"` ExceptionStorageClasses []ExceptionResource `json:"exceptionStorageClasses"` + ExceptionJobs []ExceptionResource `json:"exceptionJobs"` // Add other configurations if needed } @@ -200,7 +201,7 @@ func formatOutputForResource(resource string, resources map[string][]string, opt } } table.Render() - return fmt.Sprintf("Unused %ss:\n%s", resource, buf.String()) + return fmt.Sprintf("Unused %ss:\n%s\n", resource, buf.String()) } func formatOutputForNamespace(namespace string, resources map[string][]string, opts Opts) string { @@ -224,7 +225,7 @@ func formatOutputForNamespace(namespace string, resources map[string][]string, o return "" } table.Render() - return fmt.Sprintf("Unused resources in namespace: %q\n%s", namespace, buf.String()) + return fmt.Sprintf("Unused resources in namespace: %q\n%s\n", namespace, buf.String()) } func FormatOutputAll(namespace string, allDiffs []ResourceDiff, opts Opts) string { @@ -248,7 +249,7 @@ func FormatOutputAll(namespace string, allDiffs []ResourceDiff, opts Opts) strin return "" } table.Render() - return fmt.Sprintf("Unused resources in namespace: %q\n%s", namespace, buf.String()) + return fmt.Sprintf("Unused resources in namespace: %q\n%s\n", namespace, buf.String()) } // TODO create formatter by resource "#", "Resource Name", "Namespace" diff --git a/pkg/kor/multi.go b/pkg/kor/multi.go index 17ced677..09086537 100644 --- a/pkg/kor/multi.go +++ b/pkg/kor/multi.go @@ -109,7 +109,6 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset if len(diff.diff) != 0 { output := FormatOutputAll("", []ResourceDiff{diff}, opts) outputBuffer.WriteString(output) - outputBuffer.WriteString("\n") resourceMap := make(map[string][]string) resourceMap[diff.resourceType] = diff.diff @@ -139,7 +138,6 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset output := FormatOutputAll(namespace, allDiffs, opts) if output != "" { outputBuffer.WriteString(output) - outputBuffer.WriteString("\n") resourceMap := make(map[string][]string) for _, diff := range allDiffs { @@ -147,7 +145,6 @@ func GetUnusedMulti(resourceNames string, filterOpts *filters.Options, clientset } response[namespace] = resourceMap } - } jsonResponse, err := json.MarshalIndent(response, "", " ") diff --git a/pkg/kor/secrets.go b/pkg/kor/secrets.go index 7680d559..7c824ecf 100644 --- a/pkg/kor/secrets.go +++ b/pkg/kor/secrets.go @@ -19,6 +19,7 @@ import ( var exceptionSecretTypes = []string{ `helm.sh/release.v1`, `kubernetes.io/dockerconfigjson`, + `kubernetes.io/dockercfg`, `kubernetes.io/service-account-token`, }