Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kor for openSUSE:Factory checked in at 2026-04-23 17:07:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kor (Old) and /work/SRC/openSUSE:Factory/.kor.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kor" Thu Apr 23 17:07:55 2026 rev:34 rq:1348859 version:0.6.8 Changes: -------- --- /work/SRC/openSUSE:Factory/kor/kor.changes 2026-01-15 16:49:13.283621820 +0100 +++ /work/SRC/openSUSE:Factory/.kor.new.11940/kor.changes 2026-04-23 17:12:28.496087178 +0200 @@ -1,0 +2,24 @@ +Thu Apr 23 05:30:00 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 0.6.8: + * chore(deps): bump k8s.io/apiextensions-apiserver from 0.35.3 to + 0.35.4 (#592) + * feat: Include cluster name in the output (#586) + * chore(deps): bump github.com/fatih/color from 1.18.0 to 1.19.0 + (#587) + * chore(deps): bump k8s.io/apiextensions-apiserver from 0.35.2 to + 0.35.3 (#585) + * Add trending to README.md (#578) + * chore(deps): bump k8s.io/apiextensions-apiserver from 0.35.1 to + 0.35.2 (#574) + * feat: customizable RBAC templates (#563) + * chore(deps): bump k8s.io/apiextensions-apiserver from 0.35.0 to + 0.35.1 (#568) + * fix: improve Slack integration (#558) + * Add values to chart: image pull policy / image pull secret / + security contexts (#559) + * Fix `env` indentation inside `CronJob` template of Helm chart + (#557) + * Refactor GetConfig function for kubeconfig handling (#554) + +------------------------------------------------------------------- Old: ---- kor-0.6.7.obscpio New: ---- kor-0.6.8.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kor.spec ++++++ --- /var/tmp/diff_new_pack.eh5UZS/_old 2026-04-23 17:12:30.116153896 +0200 +++ /var/tmp/diff_new_pack.eh5UZS/_new 2026-04-23 17:12:30.116153896 +0200 @@ -17,7 +17,7 @@ Name: kor -Version: 0.6.7 +Version: 0.6.8 Release: 0 Summary: Tool to discover unused Kubernetes Resources License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.eh5UZS/_old 2026-04-23 17:12:30.156155544 +0200 +++ /var/tmp/diff_new_pack.eh5UZS/_new 2026-04-23 17:12:30.156155544 +0200 @@ -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.6.7</param> + <param name="revision">v0.6.8</param> <param name="match-tag">v*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.eh5UZS/_old 2026-04-23 17:12:30.240159003 +0200 +++ /var/tmp/diff_new_pack.eh5UZS/_new 2026-04-23 17:12:30.256159662 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/yonahd/kor</param> - <param name="changesrevision">cbd4f6b9b559599353cfb37c65b0878df25c2981</param></service></servicedata> + <param name="changesrevision">d6986c937ce72d331fef965c1b103fab69b4ea93</param></service></servicedata> (No newline at EOF) ++++++ kor-0.6.7.obscpio -> kor-0.6.8.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/README.md new/kor-0.6.8/README.md --- old/kor-0.6.7/README.md 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/README.md 2026-04-22 19:52:08.000000000 +0200 @@ -6,6 +6,7 @@ [](https://discord.gg/ajptYPwcJY) [](https://korpro.io) +<a href="https://trendshift.io/repositories/3513" target="_blank"><img src="https://trendshift.io/api/badge/repositories/3513" alt="yonahd%2Fkor | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> # Kor - Kubernetes Orphaned Resources Finder diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/Chart.yaml new/kor-0.6.8/charts/kor/Chart.yaml --- old/kor-0.6.7/charts/kor/Chart.yaml 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/Chart.yaml 2026-04-22 19:52:08.000000000 +0200 @@ -2,8 +2,8 @@ name: kor description: A Kubernetes Helm Chart to discover orphaned resources using kor type: application -version: 0.2.11 -appVersion: "0.6.7" +version: 0.2.15 +appVersion: "0.6.8" maintainers: - name: "yonahd" url: "https://github.com/yonahd/kor" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/README.md new/kor-0.6.8/charts/kor/README.md --- old/kor-0.6.7/charts/kor/README.md 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/README.md 2026-04-22 19:52:08.000000000 +0200 @@ -1,6 +1,6 @@ # kor -   +   A Kubernetes Helm Chart to discover orphaned resources using kor @@ -21,10 +21,14 @@ | cronJob.failedJobsHistoryLimit | int | `2` | | | cronJob.image.repository | string | `"yonahdissen/kor"` | | | cronJob.image.tag | string | `"latest"` | | +| cronJob.imagePullPolicy | string | `"Always"` | | +| cronJob.imagePullSecrets | list | `[]` | | | cronJob.name | string | `"kor"` | | | cronJob.namespaced | string | `nil` | Set true/false to explicitly return namespaced/non-namespaced resources | +| cronJob.podSecurityContext | object | `{}` | | | cronJob.restartPolicy | string | `"OnFailure"` | | | cronJob.schedule | string | `"0 1 * * 1"` | | +| cronJob.securityContext | object | `{}` | | | cronJob.slackAuthToken | string | `""` | | | cronJob.slackChannel | string | `""` | | | cronJob.slackWebhookUrl | string | `""` | | @@ -58,6 +62,8 @@ | 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 | +| rbac.create | bool | `true` | Create Role and/or ClusterRole (true, false, "clusterrole" or "role") | +| rbac.rules | list | `[{"apiGroups":[""],"resources":["pods","configmaps","secrets","services","serviceaccounts","persistentvolumeclaims","endpoints","namespaces","persistentvolumes"]},{"apiGroups":["apps"],"resources":["deployments","statefulsets","replicasets","daemonsets"]},{"apiGroups":["networking.k8s.io"],"resources":["ingresses","networkpolicies"]},{"apiGroups":["rbac.authorization.k8s.io"],"resources":["roles","rolebindings","clusterroles","clusterrolebindings"]},{"apiGroups":["autoscaling"],"resources":["horizontalpodautoscalers"]},{"apiGroups":["policy"],"resources":["poddisruptionbudgets"]},{"apiGroups":["batch"],"resources":["jobs"]},{"apiGroups":["discovery.k8s.io"],"resources":["endpointslices"]},{"apiGroups":["storage.k8s.io"],"resources":["storageclasses","volumeattachments"]},{"apiGroups":["scheduling.k8s.io"],"resources":["priorityclasses"]},{"apiGroups":["apiextensions.k8s.io"],"resources":["customresourcedefinitions"]}]` | Verbs default to [get, list, watch] if not specified | | 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 | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/templates/_helpers.tpl new/kor-0.6.8/charts/kor/templates/_helpers.tpl --- old/kor-0.6.7/charts/kor/templates/_helpers.tpl 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/templates/_helpers.tpl 2026-04-22 19:52:08.000000000 +0200 @@ -74,3 +74,41 @@ {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Check if Role should be created +*/}} +{{- define "kor.createRole" -}} +{{- $create := .Values.rbac.create -}} +{{- if or (eq (toString $create) "true") (eq $create "role") }}true{{- end }} +{{- end }} + +{{/* +Check if ClusterRole should be created +*/}} +{{- define "kor.createClusterRole" -}} +{{- $create := .Values.rbac.create -}} +{{- if or (eq (toString $create) "true") (eq $create "clusterrole") }}true{{- end }} +{{- end }} + +{{/* +Generate resource rules +*/}} +{{- define "kor.resourceRules" -}} +{{- range .Values.rbac.rules }} +- apiGroups: +{{- range .apiGroups }} + - {{ . | quote }} +{{- end }} + resources: + {{- toYaml .resources | nindent 4 }} + verbs: + {{- if .verbs }} + {{- toYaml .verbs | nindent 4 }} + {{- else }} + - get + - list + - watch + {{- end }} +{{- end }} +{{- end }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/templates/cronjob.yaml new/kor-0.6.8/charts/kor/templates/cronjob.yaml --- old/kor-0.6.7/charts/kor/templates/cronjob.yaml 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/templates/cronjob.yaml 2026-04-22 19:52:08.000000000 +0200 @@ -16,10 +16,19 @@ {{- include "kor.labels" . | nindent 12 }} name: {{ .Release.Name }} spec: + {{- with .Values.cronJob.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} serviceAccountName: {{ include "kor.serviceAccountName" . }} containers: - name: {{ .Release.Name }}-container image: {{ .Values.cronJob.image.repository }}:{{ .Values.cronJob.image.tag }} + imagePullPolicy: {{ .Values.cronJob.imagePullPolicy }} + {{- with .Values.cronJob.securityContext }} + securityContext: + {{ toYaml . | nindent 16 }} + {{- end }} resources: {{- toYaml .Values.cronJob.resources | nindent 16 }} command: @@ -48,9 +57,13 @@ {{- end }} {{- if .Values.cronJob.env }} env: - {{- toYaml .Values.cronJob.env | nindent 12 }} + {{- toYaml .Values.cronJob.env | nindent 16 }} {{- end }} restartPolicy: {{ .Values.cronJob.restartPolicy }} + {{- with .Values.cronJob.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} {{- if .Values.cronJob.successfulJobsHistoryLimit }} successfulJobsHistoryLimit: {{ .Values.cronJob.successfulJobsHistoryLimit }} {{- end }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/templates/role.yaml new/kor-0.6.8/charts/kor/templates/role.yaml --- old/kor-0.6.7/charts/kor/templates/role.yaml 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/templates/role.yaml 2026-04-22 19:52:08.000000000 +0200 @@ -1,3 +1,4 @@ +{{- if include "kor.createRole" . }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -6,31 +7,9 @@ labels: {{- include "kor.labels" . | nindent 4 }} rules: - - apiGroups: ["*"] - resources: - - pods - - configmaps - - secrets - - services - - serviceaccounts - - deployments - - statefulsets - - roles - - rolebindings - - horizontalpodautoscalers - - persistentvolumeclaims - - ingresses - - poddisruptionbudgets - - endpoints - - endpointslices - - jobs - - replicasets - - daemonsets - - networkpolicies - verbs: - - get - - list - - watch +{{ include "kor.resourceRules" . | nindent 2 }} +{{- end }} +{{- if include "kor.createClusterRole" . }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -39,37 +18,5 @@ labels: {{- include "kor.labels" . | nindent 4 }} rules: - - apiGroups: ["*"] - resources: - - pods - - configmaps - - secrets - - services - - serviceaccounts - - deployments - - statefulsets - - roles - - rolebindings - - horizontalpodautoscalers - - persistentvolumeclaims - - ingresses - - poddisruptionbudgets - - endpoints - - endpointslices - - jobs - - replicasets - - daemonsets - - networkpolicies - {{/* cluster-scoped resources */}} - - namespaces - - clusterroles - - clusterrolebindings - - persistentvolumes - - customresourcedefinitions - - storageclasses - - volumeattachments - - priorityclasses - verbs: - - get - - list - - watch +{{ include "kor.resourceRules" . | nindent 2 }} +{{- end }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/templates/rolebinding.yaml new/kor-0.6.8/charts/kor/templates/rolebinding.yaml --- old/kor-0.6.7/charts/kor/templates/rolebinding.yaml 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/templates/rolebinding.yaml 2026-04-22 19:52:08.000000000 +0200 @@ -1,3 +1,4 @@ +{{- if include "kor.createRole" . }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding @@ -12,6 +13,8 @@ kind: Role name: {{ include "kor.serviceAccountName" . }}-read-resources-role apiGroup: rbac.authorization.k8s.io +{{- end }} +{{- if include "kor.createClusterRole" . }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -27,3 +30,4 @@ kind: ClusterRole name: {{ include "kor.serviceAccountName" . }}-read-resources-clusterrole apiGroup: rbac.authorization.k8s.io +{{- end }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/charts/kor/values.yaml new/kor-0.6.8/charts/kor/values.yaml --- old/kor-0.6.7/charts/kor/values.yaml 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/charts/kor/values.yaml 2026-04-22 19:52:08.000000000 +0200 @@ -8,6 +8,7 @@ # limits: # memory: "32Mi" # cpu: "10m" + securityContext: {} schedule: "0 1 * * 1" image: repository: yonahdissen/kor @@ -23,6 +24,9 @@ slackChannel: "" slackAuthToken: "" restartPolicy: OnFailure + imagePullPolicy: Always + imagePullSecrets: [] + podSecurityContext: {} successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 2 @@ -97,3 +101,42 @@ # -- The name of the service account to use. # -- If not set and create is true, a name is generated using the fullname template name: "" + +rbac: + # -- Create Role and/or ClusterRole (true, false, "clusterrole" or "role") + create: true + # -- RBAC rules with proper API group to resource mappings + # -- Verbs default to [get, list, watch] if not specified + rules: + - apiGroups: [""] + resources: ["pods", "configmaps", "secrets", "services", "serviceaccounts", "persistentvolumeclaims", "endpoints", "namespaces", "persistentvolumes"] + + - apiGroups: ["apps"] + resources: ["deployments", "statefulsets", "replicasets", "daemonsets"] + + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses", "networkpolicies"] + + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"] + + - apiGroups: ["autoscaling"] + resources: ["horizontalpodautoscalers"] + + - apiGroups: ["policy"] + resources: ["poddisruptionbudgets"] + + - apiGroups: ["batch"] + resources: ["jobs"] + + - apiGroups: ["discovery.k8s.io"] + resources: ["endpointslices"] + + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "volumeattachments"] + + - apiGroups: ["scheduling.k8s.io"] + resources: ["priorityclasses"] + + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/all.go new/kor-0.6.8/cmd/kor/all.go --- old/kor-0.6.7/cmd/kor/all.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/all.go 2026-04-22 19:52:08.000000000 +0200 @@ -22,7 +22,7 @@ if response, err := kor.GetUnusedAll(filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/clusterrolebindings.go new/kor-0.6.8/cmd/kor/clusterrolebindings.go --- old/kor-0.6.7/cmd/kor/clusterrolebindings.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/clusterrolebindings.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedClusterRoleBindings(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/clusterroles.go new/kor-0.6.8/cmd/kor/clusterroles.go --- old/kor-0.6.7/cmd/kor/clusterroles.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/clusterroles.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedClusterRoles(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/configmaps.go new/kor-0.6.8/cmd/kor/configmaps.go --- old/kor-0.6.7/cmd/kor/configmaps.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/configmaps.go 2026-04-22 19:52:08.000000000 +0200 @@ -16,10 +16,11 @@ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { clientset := kor.GetKubeClient(kubeconfig) + if response, err := kor.GetUnusedConfigmaps(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/crds.go new/kor-0.6.8/cmd/kor/crds.go --- old/kor-0.6.7/cmd/kor/crds.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/crds.go 2026-04-22 19:52:08.000000000 +0200 @@ -17,10 +17,11 @@ Run: func(cmd *cobra.Command, args []string) { apiExtClient := kor.GetAPIExtensionsClient(kubeconfig) dynamicClient := kor.GetDynamicClient(kubeconfig) + if response, err := kor.GetUnusedCrds(filterOptions, apiExtClient, dynamicClient, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/daemonsets.go new/kor-0.6.8/cmd/kor/daemonsets.go --- old/kor-0.6.7/cmd/kor/daemonsets.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/daemonsets.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedDaemonSets(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/deployments.go new/kor-0.6.8/cmd/kor/deployments.go --- old/kor-0.6.7/cmd/kor/deployments.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/deployments.go 2026-04-22 19:52:08.000000000 +0200 @@ -16,10 +16,11 @@ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { clientset := kor.GetKubeClient(kubeconfig) + if response, err := kor.GetUnusedDeployments(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/finalizers.go new/kor-0.6.8/cmd/kor/finalizers.go --- old/kor-0.6.7/cmd/kor/finalizers.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/finalizers.go 2026-04-22 19:52:08.000000000 +0200 @@ -6,6 +6,7 @@ "github.com/spf13/cobra" "github.com/yonahd/kor/pkg/kor" + "github.com/yonahd/kor/pkg/utils" ) var finalizerCmd = &cobra.Command{ @@ -20,6 +21,7 @@ if response, err := kor.GetUnusedfinalizers(filterOptions, clientset, dynamicClient, outputFormat, opts); err != nil { fmt.Println(err) } else { + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/hpas.go new/kor-0.6.8/cmd/kor/hpas.go --- old/kor-0.6.7/cmd/kor/hpas.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/hpas.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedHpas(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/ingresses.go new/kor-0.6.8/cmd/kor/ingresses.go --- old/kor-0.6.7/cmd/kor/ingresses.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/ingresses.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedIngresses(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/jobs.go new/kor-0.6.8/cmd/kor/jobs.go --- old/kor-0.6.7/cmd/kor/jobs.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/jobs.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedJobs(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/networkpolicies.go new/kor-0.6.8/cmd/kor/networkpolicies.go --- old/kor-0.6.7/cmd/kor/networkpolicies.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/networkpolicies.go 2026-04-22 19:52:08.000000000 +0200 @@ -16,10 +16,11 @@ Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { clientset := kor.GetKubeClient(kubeconfig) + if response, err := kor.GetUnusedNetworkPolicies(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/pdbs.go new/kor-0.6.8/cmd/kor/pdbs.go --- old/kor-0.6.7/cmd/kor/pdbs.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/pdbs.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedPdbs(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/pods.go new/kor-0.6.8/cmd/kor/pods.go --- old/kor-0.6.7/cmd/kor/pods.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/pods.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedPods(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/priorityclasses.go new/kor-0.6.8/cmd/kor/priorityclasses.go --- old/kor-0.6.7/cmd/kor/priorityclasses.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/priorityclasses.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedPriorityClasses(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/pv.go new/kor-0.6.8/cmd/kor/pv.go --- old/kor-0.6.7/cmd/kor/pv.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/pv.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedPvs(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/pvc.go new/kor-0.6.8/cmd/kor/pvc.go --- old/kor-0.6.7/cmd/kor/pvc.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/pvc.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedPvcs(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/replicasets.go new/kor-0.6.8/cmd/kor/replicasets.go --- old/kor-0.6.7/cmd/kor/replicasets.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/replicasets.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedReplicaSets(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/rolebindings.go new/kor-0.6.8/cmd/kor/rolebindings.go --- old/kor-0.6.7/cmd/kor/rolebindings.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/rolebindings.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedRoleBindings(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/roles.go new/kor-0.6.8/cmd/kor/roles.go --- old/kor-0.6.7/cmd/kor/roles.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/roles.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedRoles(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/root.go new/kor-0.6.8/cmd/kor/root.go --- old/kor-0.6.7/cmd/kor/root.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/root.go 2026-04-22 19:52:08.000000000 +0200 @@ -35,11 +35,16 @@ return nil } + if opts.ClusterName == "" { + opts.ClusterName = kor.GetClusterName(kubeconfig) + } + initKindsList() return nil }, Run: func(cmd *cobra.Command, args []string) { resourceNames := args[0] + clientset := kor.GetKubeClient(kubeconfig) apiExtClient := kor.GetAPIExtensionsClient(kubeconfig) dynamicClient := kor.GetDynamicClient(kubeconfig) @@ -47,7 +52,7 @@ if response, err := kor.GetUnusedMulti(resourceNames, filterOptions, clientset, apiExtClient, dynamicClient, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, @@ -80,6 +85,7 @@ rootCmd.PersistentFlags().StringVar(&opts.WebhookURL, "slack-webhook-url", "", "Slack webhook URL to send notifications to") rootCmd.PersistentFlags().StringVar(&opts.Channel, "slack-channel", "", "Slack channel to send notifications to, requires --slack-auth-token to be set") rootCmd.PersistentFlags().StringVar(&opts.Token, "slack-auth-token", "", "Slack auth token to send notifications to, requires --slack-channel to be set") + rootCmd.PersistentFlags().StringVar(&opts.ClusterName, "cluster-name", "", "Override the cluster name shown in CLI output and Slack notifications") rootCmd.PersistentFlags().BoolVar(&opts.DeleteFlag, "delete", false, "Delete unused resources") rootCmd.PersistentFlags().BoolVar(&opts.NoInteractive, "no-interactive", false, "Do not prompt for confirmation when deleting resources. Be careful when using this flag!") rootCmd.PersistentFlags().BoolVarP(&opts.Verbose, "verbose", "v", false, "Verbose output (print empty namespaces)") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/secrets.go new/kor-0.6.8/cmd/kor/secrets.go --- old/kor-0.6.7/cmd/kor/secrets.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/secrets.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedSecrets(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/serviceaccounts.go new/kor-0.6.8/cmd/kor/serviceaccounts.go --- old/kor-0.6.7/cmd/kor/serviceaccounts.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/serviceaccounts.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedServiceAccounts(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/services.go new/kor-0.6.8/cmd/kor/services.go --- old/kor-0.6.7/cmd/kor/services.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/services.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedServices(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/statefulsets.go new/kor-0.6.8/cmd/kor/statefulsets.go --- old/kor-0.6.7/cmd/kor/statefulsets.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/statefulsets.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedStatefulSets(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/storageclasses.go new/kor-0.6.8/cmd/kor/storageclasses.go --- old/kor-0.6.7/cmd/kor/storageclasses.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/storageclasses.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedStorageClasses(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/version.go new/kor-0.6.8/cmd/kor/version.go --- old/kor-0.6.7/cmd/kor/version.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/version.go 2026-04-22 19:52:08.000000000 +0200 @@ -11,7 +11,7 @@ Short: "Print kor version information", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - utils.PrintVersion() + utils.PrintVersion("") }, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/cmd/kor/volumeattachments.go new/kor-0.6.8/cmd/kor/volumeattachments.go --- old/kor-0.6.7/cmd/kor/volumeattachments.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/cmd/kor/volumeattachments.go 2026-04-22 19:52:08.000000000 +0200 @@ -20,7 +20,7 @@ if response, err := kor.GetUnusedVolumeAttachments(filterOptions, clientset, outputFormat, opts); err != nil { fmt.Println(err) } else { - utils.PrintLogo(outputFormat) + utils.PrintLogo(outputFormat, opts.ClusterName) fmt.Println(response) } }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/go.mod new/kor-0.6.8/go.mod --- old/kor-0.6.7/go.mod 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/go.mod 2026-04-22 19:52:08.000000000 +0200 @@ -3,15 +3,16 @@ go 1.25.0 require ( - github.com/fatih/color v1.18.0 + github.com/fatih/color v1.19.0 + github.com/jarcoal/httpmock v1.4.1 github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 - k8s.io/api v0.35.0 - k8s.io/apiextensions-apiserver v0.35.0 - k8s.io/apimachinery v0.35.0 - k8s.io/client-go v0.35.0 + k8s.io/api v0.35.4 + k8s.io/apiextensions-apiserver v0.35.4 + k8s.io/apimachinery v0.35.4 + k8s.io/client-go v0.35.4 k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/yaml v1.6.0 ) @@ -34,7 +35,7 @@ github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -56,7 +57,7 @@ go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.9.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/go.sum new/kor-0.6.8/go.sum --- old/kor-0.6.7/go.sum 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/go.sum 2026-04-22 19:52:08.000000000 +0200 @@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -45,6 +45,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= +github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -62,14 +64,15 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI= +github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -146,10 +149,9 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= @@ -170,14 +172,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= -k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= -k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= -k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= +k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= +k8s.io/apiextensions-apiserver v0.35.4 h1:HeP+Upp7ItdvnyGmub0yoix+2z5+ev4M5cE5TCgtOUU= +k8s.io/apiextensions-apiserver v0.35.4/go.mod h1:ogQlk+stIE8mnoRthSYCwlOS12fVqgWFiErMwPaXA7c= +k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= +k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= +k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= +k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/common/params.go new/kor-0.6.8/pkg/common/params.go --- old/kor-0.6.7/pkg/common/params.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/common/params.go 2026-04-22 19:52:08.000000000 +0200 @@ -4,6 +4,7 @@ DeleteFlag bool NoInteractive bool Verbose bool + ClusterName string WebhookURL string Channel string Token string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/kor/formatter.go new/kor-0.6.8/pkg/kor/formatter.go --- old/kor-0.6.7/pkg/kor/formatter.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/kor/formatter.go 2026-04-22 19:52:08.000000000 +0200 @@ -31,9 +31,14 @@ if opts.WebhookURL == "" && (opts.Channel == "" || opts.Token == "") { return outputBuffer.String(), nil } - if err := utils.SendToSlack(utils.SlackMessage{}, opts, outputBuffer.String()); err != nil { + slackOutput := outputBuffer.String() + if opts.ClusterName != "" { + slackOutput = fmt.Sprintf("Cluster: %s\n\n%s", opts.ClusterName, slackOutput) + } + if err := utils.SendToSlack(utils.SlackMessage{}, opts, slackOutput); err != nil { return "", fmt.Errorf("failed to send message to slack: %w", err) } + return outputBuffer.String(), nil case "json", "yaml": var resources map[string]map[string][]ResourceInfo if err := json.Unmarshal(jsonResponse, &resources); err != nil { @@ -81,7 +86,6 @@ default: return "", fmt.Errorf("unsupported output format: %s", outputFormat) } - return "", fmt.Errorf("unsupported output format: %s", outputFormat) } func FormatOutput(resources map[string]map[string][]ResourceInfo, opts common.Opts) bytes.Buffer { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/kor/kor.go new/kor-0.6.8/pkg/kor/kor.go --- old/kor-0.6.7/pkg/kor/kor.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/kor/kor.go 2026-04-22 19:52:08.000000000 +0200 @@ -68,15 +68,42 @@ return filepath.Join(home, ".kube", "config") } -func GetConfig(kubeconfig string) (*rest.Config, error) { - if _, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token"); err == nil { - return rest.InClusterConfig() +func isRunningInCluster() bool { + _, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token") + return err == nil +} + +func GetClusterName(kubeconfig string) string { + if isRunningInCluster() { + return "" + } + + loader := clientcmd.NewDefaultClientConfigLoadingRules() + loader.ExplicitPath = kubeconfig + overrides := &clientcmd.ConfigOverrides{} + config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides) + rawConfig, err := config.RawConfig() + if err != nil { + return "" } + contexts, ok := rawConfig.Contexts[rawConfig.CurrentContext] + if !ok || contexts.Cluster == "" { + return "" + } + + return contexts.Cluster +} + +func GetConfig(kubeconfig string) (*rest.Config, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() if kubeconfig != "" { loadingRules.ExplicitPath = kubeconfig + } else { + if isRunningInCluster() { + return rest.InClusterConfig() + } } configOverrides := &clientcmd.ConfigOverrides{} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/kor/kor_test.go new/kor-0.6.8/pkg/kor/kor_test.go --- old/kor-0.6.7/pkg/kor/kor_test.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/kor/kor_test.go 2026-04-22 19:52:08.000000000 +0200 @@ -156,6 +156,33 @@ } } +func TestGetClusterName(t *testing.T) { + configFile, err := os.CreateTemp("", "kubeconfig-") + if err != nil { + t.Error(err) + } + defer func() { + if err := os.Remove(configFile.Name()); err != nil { + t.Logf("failed to remove temp file: %v", err) + } + }() + if err := os.WriteFile(configFile.Name(), []byte(getFakeConfigContent()), 0666); err != nil { + t.Error(err) + } + + clusterName := GetClusterName(configFile.Name()) + if clusterName != "foo-cluster" { + t.Errorf("Expected %q, got %q", "foo-cluster", clusterName) + } +} + +func TestGetClusterNameInvalidPath(t *testing.T) { + clusterName := GetClusterName("/nonexistent/kubeconfig") + if clusterName != "" { + t.Errorf("Expected empty string, got %q", clusterName) + } +} + func getFakeExceptions() []ExceptionResource { return []ExceptionResource{ { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/utils/banner.go new/kor-0.6.8/pkg/utils/banner.go --- old/kor-0.6.7/pkg/utils/banner.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/utils/banner.go 2026-04-22 19:52:08.000000000 +0200 @@ -8,11 +8,14 @@ var Version = "dev" -func PrintVersion() { +func PrintVersion(cluster string) { fmt.Printf("kor version: v%s\n", Version) + if cluster != "" { + fmt.Printf("kor cluster: %s\n", cluster) + } } -func PrintLogo(outputFormat string) { +func PrintLogo(outputFormat string, cluster string) { boldBlue := color.New(color.FgHiBlue, color.Bold) asciiLogo := ` _ _____ ____ @@ -22,11 +25,12 @@ |_|\_\___/|_| \_\ Powered by Korpro.io + Reach out for full scan and cost analysis ` // processing of the `outputFormat` happens inside of the rootCmd so this requires a pretty large change // to keep the banner. Instead just loop through os args and find if the format was set and handle it there if outputFormat != "yaml" && outputFormat != "json" { - PrintVersion() + PrintVersion(cluster) _, _ = boldBlue.Println(asciiLogo) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/utils/slack.go new/kor-0.6.8/pkg/utils/slack.go --- old/kor-0.6.7/pkg/utils/slack.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/utils/slack.go 2026-04-22 19:52:08.000000000 +0200 @@ -6,20 +6,30 @@ "errors" "fmt" "io" - "mime/multipart" "net/http" - "os" - "path/filepath" + "time" "github.com/yonahd/kor/pkg/common" ) +const slackEndpointURL = "https://slack.com/api/chat.postMessage" + +var client = &http.Client{ + Timeout: 5 * time.Minute, +} + type SendMessageToSlack interface { SendToSlack(opts common.Opts, outputBuffer string) error } type SlackPayload struct { - Text string `json:"text"` + Text string `json:"text"` + Channel string `json:"channel,omitempty"` +} + +type SlackAPIResponse struct { + Ok bool `json:"ok"` + Error string `json:"error,omitempty"` } type SlackMessage struct { @@ -30,105 +40,88 @@ } func (sm SlackMessage) SendToSlack(opts common.Opts, outputBuffer string) error { - if opts.WebhookURL != "" { + if outputBuffer == "" { + return nil + } - // Prepare payload safely - slackPayload := SlackPayload{Text: outputBuffer} - payload, err := json.Marshal(slackPayload) - if err != nil { - return fmt.Errorf("failed to marshal Slack payload: %w", err) + if opts.WebhookURL != "" { + slackPayload := SlackPayload{ + Text: outputBuffer, } - - _, err = http.Post(opts.WebhookURL, "application/json", bytes.NewBuffer(payload)) - + payload, err := json.Marshal(slackPayload) if err != nil { - return err + return fmt.Errorf("failed to marshal payload: %w", err) } - return nil - } else if opts.Channel != "" && opts.Token != "" { - fmt.Printf("Sending message to Slack channel %s...", opts.Channel) - outputFilePath, _ := writeOutputToFile(outputBuffer) - - var formData bytes.Buffer - writer := multipart.NewWriter(&formData) - fileWriter, err := writer.CreateFormFile("file", outputFilePath) - if err != nil { - return err - } - file, err := os.Open(outputFilePath) + response, err := client.Post(opts.WebhookURL, "application/json", bytes.NewBuffer(payload)) if err != nil { return err } defer func() { - if err := file.Close(); err != nil { - fmt.Printf("failed to close file: %v\n", err) + if err := response.Body.Close(); err != nil { + fmt.Printf("failed to close response body: %v\n", err) } }() - _, err = io.Copy(fileWriter, file) + + _, err = io.ReadAll(response.Body) if err != nil { - return err + return fmt.Errorf("failed to read response body: %w", err) } - if err := writer.WriteField("channels", opts.Channel); err != nil { - return err + if response.StatusCode != http.StatusOK { + return fmt.Errorf("non-OK status code: %d", response.StatusCode) } - if err := writer.Close(); err != nil { - return err + return nil + } else if opts.Channel != "" && opts.Token != "" { + fmt.Printf("Sending message to Slack channel %s...\n", opts.Channel) + slackPayload := SlackPayload{ + Text: outputBuffer, + Channel: opts.Channel, + } + + payload, err := json.Marshal(slackPayload) + if err != nil { + return fmt.Errorf("failed to marshal payload: %w", err) } - req, err := http.NewRequest("POST", "https://slack.com/api/files.upload", &formData) + request, err := http.NewRequest(http.MethodPost, slackEndpointURL, bytes.NewBuffer(payload)) if err != nil { return err } - req.Header.Set("Authorization", "Bearer "+opts.Token) - req.Header.Set("Content-Type", writer.FormDataContentType()) + request.Header.Set("Authorization", "Bearer "+opts.Token) + request.Header.Set("Content-Type", "application/json") - client := &http.Client{} - resp, err := client.Do(req) + response, err := client.Do(request) if err != nil { return err } defer func() { - if err := resp.Body.Close(); err != nil { + if err := response.Body.Close(); err != nil { fmt.Printf("failed to close response body: %v\n", err) } }() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("slack API returned non-OK status code: %d", resp.StatusCode) + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) } - return nil - } else { - return errors.New("SlackOpts must contain either WebhookURL or Channel and Token") - } -} + if response.StatusCode != http.StatusOK { + return fmt.Errorf("non-OK status code: %d, body: %s", response.StatusCode, string(body)) + } -func writeOutputToFile(outputBuffer string) (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("failed to get user's home directory: %v", err) - } + var slackResp SlackAPIResponse + if err := json.Unmarshal(body, &slackResp); err != nil { + return fmt.Errorf("failed to parse response: %w", err) + } - outputFileName := "kor-scan-results.txt" - outputFilePath := filepath.Join(homeDir, outputFileName) + if !slackResp.Ok { + return fmt.Errorf("API error: %s", slackResp.Error) + } - file, err := os.Create(outputFilePath) - if err != nil { - return "", fmt.Errorf("failed to create output file: %v", err) - } - defer func() { - if err := file.Close(); err != nil { - fmt.Printf("failed to close file: %v\n", err) - } - }() - - _, err = file.WriteString(outputBuffer) - if err != nil { - return "", fmt.Errorf("failed to write output to file: %v", err) + return nil + } else { + return errors.New("SlackOpts must contain either WebhookURL or Channel and Token") } - - return outputFilePath, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kor-0.6.7/pkg/utils/slack_test.go new/kor-0.6.8/pkg/utils/slack_test.go --- old/kor-0.6.7/pkg/utils/slack_test.go 2026-01-14 20:46:48.000000000 +0100 +++ new/kor-0.6.8/pkg/utils/slack_test.go 2026-04-22 19:52:08.000000000 +0200 @@ -1,76 +1,245 @@ package utils import ( - "bytes" + "encoding/json" "net/http" - "net/http/httptest" - "os" + "strings" "testing" + "github.com/jarcoal/httpmock" + "github.com/yonahd/kor/pkg/common" ) -type SendToSlackTestCase struct { - Name string - Opts common.Opts - OutputBuffer string -} - -var testCases = []SendToSlackTestCase{ - { - Name: "Test using WebhookURL", - Opts: common.Opts{ - WebhookURL: "slack.webhookurl.com", - }, - OutputBuffer: "Test message", - }, - { - Name: "Test using Channel and Token", - Opts: common.Opts{ - Channel: "your_channel", - Token: "your_token", - }, - OutputBuffer: "Test message", - }, - { - Name: "Test with empty Opts", - Opts: common.Opts{}, - OutputBuffer: "Test message", - }, +const ( + channel = "test" + outputBuffer = "Test!" + token = "xoxb-..." + webhookURL = "https://hooks.slack.com/services/test" + endpointURL = slackEndpointURL +) + +func TestSendToSlack_EmptyBuffer(t *testing.T) { + opts := common.Opts{ + WebhookURL: webhookURL, + } + + err := SendToSlack(SlackMessage{}, opts, "") + if err != nil { + t.Errorf("Expected nil for empty buffer, got %v", err) + } } -func TestSendToSlack(t *testing.T) { +func TestSendToSlack_BadOpts(t *testing.T) { + testCases := []struct { + Name string + Opts common.Opts + }{ + { + Name: "Empty options", + Opts: common.Opts{}, + }, + { + Name: "No channel", + Opts: common.Opts{ + Token: token, + }, + }, + { + Name: "No token", + Opts: common.Opts{ + Channel: channel, + }, + }, + } + for _, tc := range testCases { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := SendToSlack(SlackMessage{}, tc.Opts, tc.OutputBuffer); err != nil { - t.Errorf("Expected no error, got %v", err) + err := SendToSlack(SlackMessage{}, tc.Opts, outputBuffer) + if err == nil { + t.Errorf("Test %s: expected error, got nil", tc.Name) + } + } +} + +func TestSendToSlack_Webhook_Success(t *testing.T) { + httpmock.Activate() + t.Cleanup(httpmock.DeactivateAndReset) + + httpmock.RegisterResponder(http.MethodPost, webhookURL, + func(r *http.Request) (*http.Response, error) { + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Got unexpected content type: %s", contentType) + } + + var payload SlackPayload + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + t.Errorf("Failed to unmarshal payload: %v", err) + } + if payload.Channel != "" { + t.Errorf("Expected payload channel to be empty, got '%s'", payload.Channel) } - })) + if payload.Text != outputBuffer { + t.Errorf("Expected payload text to be '%s', got '%s'", outputBuffer, payload.Text) + } + + return httpmock.NewStringResponse(http.StatusOK, "ok"), nil + }, + ) - defer server.Close() + opts := common.Opts{ + WebhookURL: webhookURL, + } + + err := SendToSlack(SlackMessage{}, opts, outputBuffer) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + calls := httpmock.GetTotalCallCount() + if calls != 1 { + t.Errorf("Expected 1 HTTP call, got %d", calls) } } -func TestWriteOutputToFile(t *testing.T) { - outputBuffer := bytes.Buffer{} - outputBuffer.WriteString("This is a test output.\n") - expectedOutput := outputBuffer.String() +func TestSendToSlack_Webhook_Error(t *testing.T) { + httpmock.Activate() + t.Cleanup(httpmock.DeactivateAndReset) + + httpmock.RegisterResponder( + http.MethodPost, + webhookURL, + httpmock.NewBytesResponder( + http.StatusInternalServerError, + []byte{}, + ), + ) + + opts := common.Opts{ + WebhookURL: webhookURL, + } - outputFilePath, err := writeOutputToFile(expectedOutput) + err := SendToSlack(SlackMessage{}, opts, outputBuffer) + if err == nil { + t.Errorf("Expected error, got nil") + } + calls := httpmock.GetTotalCallCount() + if calls != 1 { + t.Errorf("Expected 1 HTTP call, got %d", calls) + } +} + +func TestSendToSlack_API_Success(t *testing.T) { + httpmock.Activate() + t.Cleanup(httpmock.DeactivateAndReset) + + httpmock.RegisterResponder(http.MethodPost, endpointURL, + func(r *http.Request) (*http.Response, error) { + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + t.Errorf("Got unexpected content type: %s", contentType) + } + authorization := r.Header.Get("Authorization") + if !strings.HasSuffix(authorization, token) { + t.Errorf("Bad authorization header format: %s", authorization) + } + + var payload SlackPayload + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + t.Errorf("Failed to unmarshal payload: %v", err) + } + + if payload.Channel != channel { + t.Errorf("Expected payload channel to be '%s', got '%s'", channel, payload.Channel) + } + if payload.Text != outputBuffer { + t.Errorf("Expected payload text to be '%s', got '%s'", outputBuffer, payload.Text) + } + + response, _ := httpmock.NewJsonResponse( + http.StatusOK, + SlackAPIResponse{ + Ok: true, + }, + ) + return response, nil + }, + ) + + opts := common.Opts{ + Token: token, + Channel: channel, + } + + err := SendToSlack(SlackMessage{}, opts, outputBuffer) if err != nil { t.Errorf("Expected no error, got %v", err) } + calls := httpmock.GetTotalCallCount() + if calls != 1 { + t.Errorf("Expected 1 HTTP call, got %d", calls) + } +} - if _, err := os.Stat(outputFilePath); os.IsNotExist(err) { - t.Errorf("Expected output file to exist, got error: %v", err) +func TestSendToSlack_API_Error(t *testing.T) { + httpmock.Activate() + t.Cleanup(httpmock.DeactivateAndReset) + + invalidAuth := "invalid_auth" + httpmock.RegisterResponder(http.MethodPost, endpointURL, + func(req *http.Request) (*http.Response, error) { + response, _ := httpmock.NewJsonResponse( + http.StatusOK, + SlackAPIResponse{ + Ok: false, + Error: invalidAuth, + }, + ) + return response, nil + }, + ) + + opts := common.Opts{ + Token: token, + Channel: channel, } - fileContent, err := os.ReadFile(outputFilePath) - if err != nil { - t.Errorf("Failed to read output file: %v", err) + err := SendToSlack(SlackMessage{}, opts, outputBuffer) + if err == nil { + t.Errorf("Expected error, got nil") } + if err != nil && !strings.HasSuffix(err.Error(), invalidAuth) { + t.Errorf("Expected error to be '%s', got '%s'", invalidAuth, err.Error()) + } + calls := httpmock.GetTotalCallCount() + if calls != 1 { + t.Errorf("Expected 1 HTTP call, got %d", calls) + } +} - if string(fileContent) != expectedOutput { - t.Errorf("Expected file content:\n%s\nGot:\n%s", expectedOutput, string(fileContent)) +func TestSendToSlack_API_NonOKStatus(t *testing.T) { + httpmock.Activate() + t.Cleanup(httpmock.DeactivateAndReset) + + httpmock.RegisterResponder( + http.MethodPost, + endpointURL, + httpmock.NewBytesResponder( + http.StatusInternalServerError, + []byte{}, + ), + ) + + opts := common.Opts{ + Token: token, + Channel: channel, + } + + err := SendToSlack(SlackMessage{}, opts, outputBuffer) + if err == nil { + t.Errorf("Expected error, got nil") + } + calls := httpmock.GetTotalCallCount() + if calls != 1 { + t.Errorf("Expected 1 HTTP call, got %d", calls) } } ++++++ kor.obsinfo ++++++ --- /var/tmp/diff_new_pack.eh5UZS/_old 2026-04-23 17:12:30.604173994 +0200 +++ /var/tmp/diff_new_pack.eh5UZS/_new 2026-04-23 17:12:30.620174653 +0200 @@ -1,5 +1,5 @@ name: kor -version: 0.6.7 -mtime: 1768420008 -commit: cbd4f6b9b559599353cfb37c65b0878df25c2981 +version: 0.6.8 +mtime: 1776880328 +commit: d6986c937ce72d331fef965c1b103fab69b4ea93 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kor/vendor.tar.gz /work/SRC/openSUSE:Factory/.kor.new.11940/vendor.tar.gz differ: char 13, line 1
