Repository: soraro/kurt
Branch: main
Commit: ff09db0b8fb9
Files: 29
Total size: 53.5 KB
Directory structure:
gitextract_tkrgsaxg/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ ├── e2e.yml
│ └── go-releaser.yml
├── .gitignore
├── .goreleaser.yml
├── .krew.yaml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd/
│ ├── auth.go
│ ├── collect.go
│ ├── collect_test.go
│ ├── commands.go
│ ├── constants.go
│ ├── printer.go
│ ├── printer_test.go
│ └── root.go
├── go.mod
├── go.sum
├── internal/
│ └── version/
│ └── version.go
├── main.go
└── tests/
├── e2e.sh
└── test.yml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: kwsorensen
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Command run [e.g. ./kurt all]
2. Information about run environment
Including
- OS: [e.g. Ubuntu]
- kurt cli version [e.g. v0.1.0]
- Confirmation of kubectl configuration/permissions
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: kwsorensen
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thanks for sending a pull request! Here are some tips for you:
1. Please file an issue before making a pull request.
2. Clarification on feature functionality should be done on the Issues page and implementation details should be discussed on the Pull Request.
-->
#### What this PR does / why we need it:
#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #<issue number>
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
================================================
FILE: .github/workflows/ci.yml
================================================
on:
pull_request:
branches:
- main
push:
branches:
- "main"
tags:
- "v*.*.*"
jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v2
with:
go-version: "1.21"
- uses: actions/checkout@v2
- run: go build
test:
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v2
with:
go-version: "1.21"
- uses: actions/checkout@v2
- run: go test ./cmd -v
lint:
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-go@v2
with:
go-version: "1.21"
- uses: actions/checkout@v2
- run: |
if
test -z $(gofmt -l .); then
echo "All golang files formatted correctly 👍️";
else
echo "❗️ Golang formatting issues:"; gofmt -l .; exit 1
fi
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '44 7 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
================================================
FILE: .github/workflows/e2e.yml
================================================
name: e2e tests
on:
pull_request:
workflow_dispatch:
jobs:
run-e2e-tests:
runs-on: ubuntu-latest
strategy:
matrix:
include:
# get these here: https://github.com/kubernetes-sigs/kind/releases
- version: "1.32"
image: kindest/node:v1.32.0@sha256:c48c62eac5da28cdadcf560d1d8616cfa6783b58f0d94cf63ad1bf49600cb027
- version: "1.31"
image: kindest/node:v1.31.4@sha256:2cb39f7295fe7eafee0842b1052a599a4fb0f8bcf3f83d96c7f4864c357c6c30
- version: "1.30"
image: kindest/node:v1.30.8@sha256:17cd608b3971338d9180b00776cb766c50d0a0b6b904ab4ff52fd3fc5c6369bf
- version: "1.29"
image: kindest/node:v1.29.2@sha256:51a1434a5397193442f0be2a297b488b6c919ce8a3931be0ce822606ea5ca245
- version: "1.28"
image: kindest/node:v1.28.7@sha256:9bc6c451a289cf96ad0bbaf33d416901de6fd632415b076ab05f5fa7e4f65c58
- version: "1.27"
image: kindest/node:v1.27.11@sha256:681253009e68069b8e01aad36a1e0fa8cf18bb0ab3e5c4069b2e65cafdd70843
- version: "1.26"
image: kindest/node:v1.26.14@sha256:5d548739ddef37b9318c70cb977f57bf3e5015e4552be4e27e57280a8cbb8e4f
steps:
- name: Create k8s Kind Cluster - ${{ matrix.version }}
uses: helm/kind-action@v1.5.0
with:
node_image: ${{ matrix.image }}
- name: Show cluster version
run: kubectl version
- uses: actions/setup-go@v2
with:
go-version: "1.21"
- uses: actions/checkout@v2
- run: go build
- name: e2e test
run: tests/e2e.sh
================================================
FILE: .github/workflows/go-releaser.yml
================================================
name: goreleaser
on:
push:
tags:
- "v*.*.*"
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.21
- name: Anchore SBOM action
uses: anchore/sbom-action@v0
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
version: latest
args: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
name: Update new version in krew-index
uses: rajatjindal/krew-release-bot@v0.0.40
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
.vscode/
**/__debug_bin
kurt
!kurt/
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
================================================
FILE: .goreleaser.yml
================================================
builds:
- id: kurt
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
ldflags: -s -w
-X kurt/internal/version.version={{.Version}}
-X kurt/internal/version.gitSHA={{.Commit}}
-X kurt/internal/version.buildTime={{.Date}}
-extldflags "-static"
checksum:
name_template: "{{ .ProjectName }}_checksums.txt"
sboms: # https://goreleaser.com/customization/sbom/
- artifacts: archive
archives:
- id: kurt
builds:
- kurt
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
================================================
FILE: .krew.yaml
================================================
apiVersion: krew.googlecontainertools.github.com/v1alpha2
kind: Plugin
metadata:
name: kurt
spec:
version: {{ .TagName }}
platforms:
- selector:
matchLabels:
os: linux
arch: amd64
{{addURIAndSha "https://github.com/soraro/kurt/releases/download/{{ .TagName }}/kurt_linux_amd64.tar.gz" .TagName }}
files:
- from: kurt
to: .
- from: LICENSE
to: .
bin: kurt
- selector:
matchLabels:
os: darwin
arch: amd64
{{addURIAndSha "https://github.com/soraro/kurt/releases/download/{{ .TagName }}/kurt_darwin_amd64.tar.gz" .TagName }}
files:
- from: kurt
to: .
- from: LICENSE
to: .
bin: kurt
- selector:
matchLabels:
os: darwin
arch: arm64
{{addURIAndSha "https://github.com/soraro/kurt/releases/download/{{ .TagName }}/kurt_darwin_arm64.tar.gz" .TagName }}
files:
- from: kurt
to: .
- from: LICENSE
to: .
bin: kurt
- selector:
matchLabels:
os: linux
arch: arm64
{{addURIAndSha "https://github.com/soraro/kurt/releases/download/{{ .TagName }}/kurt_linux_arm64.tar.gz" .TagName }}
files:
- from: kurt
to: .
- from: LICENSE
to: .
bin: kurt
- selector:
matchLabels:
os: windows
arch: amd64
{{addURIAndSha "https://github.com/soraro/kurt/releases/download/{{ .TagName }}/kurt_windows_amd64.zip" .TagName }}
files:
- from: kurt.exe
to: .
- from: LICENSE
to: .
bin: kurt.exe
shortDescription: Find what's restarting and why
homepage: https://github.com/soraro/kurt
description: |
Use kurt to see pods that are restarting in your cluster and get further context to issues by grouping the results.
Top 5 results from all groupings:
kubectl kurt all
Top 5 nodes with restarting pods:
kubectl kurt nodes
All restarting pods in the test namespace:
kubectl kurt pods -c 0 -n test
Help:
kubectl kurt -h
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[@kwsorensen](https://github.com/kwsorensen) or [@aro5000](https://github.com/aro5000).
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Create an Issue or Create a PR
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Kyle Sorensen, Aaron Stults
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# kurt
```
kurt: KUbernetes Restart Tracker
A restart tracker that gives context to what is restarting in your cluster
Usage:
kurt [command]
Available Commands:
all Print all groupings collected by kurt!
completion generate the autocompletion script for the specified shell
help Help about any command
labels Only print restart counts grouped by labels
namespaces Only print namespace-wide restart counts
nodes Only print node restart counts
pods Only print pod restart counts
version Print the current version and exit
Flags:
-h, --help help for kurt
-l, --label strings Specify multiple times for the label keys you want to see.
For example: "kurt all -l app"
-c, --limit int Limit the number of resources you want to see. Set limit to 0 for no limits. Must be positive.
For example: "kurt all -c=10" (default 5)
-n, --namespace strings Specify namespace for kurt to collect restart metrics.
Leave blank to collect in all namespaces.
-o, --output string Specify output type. Options are: json, yaml, standard
For example: "kurt all -o json" (default "standard")
Use "kurt [command] --help" for more information about a command.
```
# Install
Head over to our [releases page](https://github.com/soraro/kurt/releases/latest) or run as a `kubectl` plugin with [krew](https://krew.sigs.k8s.io/)
```
kubectl krew install kurt
```
Easily install krew and kurt with the following:
```
curl https://krew.sh/kurt | bash
```
# Examples
Show the top 5 highest restart counts grouped by `Namespace`, `Node`, `Label`, and `Pod`:
```
$ kurt all
kurt: KUbernetes Restart Tracker
==========
Namespace Restarts
default 2
test 1
kube-system 0
==========
Node Restarts
minikube-m02 2
minikube-m03 1
minikube 0
==========
Label Restarts
run:nginx 3
component:etcd 0
k8s-app:kube-proxy 0
addonmanager.kubernetes.io/mode:Reconcile 0
integration-test:storage-provisioner 0
==========
Pod Namespace Restarts
nginx default 2
nginx test 1
kube-apiserver-minikube kube-system 0
storage-provisioner kube-system 0
etcd-minikube kube-system 0
```
Show more results:
```
kurt all -c 10
# use -c 0 if you want to show all results
```
Show which node has the most restarted pods:
```
kurt no
```
Show top 20 pod restart counts in the `default` namespace which also have the `app` label key:
```
kurt po -n default -l app -c 20
```
Get help:
```
kurt -h
```
Structured output:
```
# With structured output you could use a script like this to delete the top rebooting pod
JSON=$(kurt pods -o json)
POD=$(echo $JSON | jq -r .pods[0].name)
NS=$(echo $JSON | jq -r .pods[0].namespace)
kubectl delete pod $POD -n $NS
```
# Permissions
As seen in the [`cmd/collect.go` file](https://github.com/soraro/kurt/blob/main/cmd/collect.go) the only permission required for kurt is `pods/list`.
# Requirements
Go Version 1.21
# Building
```
go build .
```
Outputs a `kurt` binary
# Testing
```
go test ./cmd -v
```
================================================
FILE: cmd/auth.go
================================================
package cmd
import (
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/clientcmd"
)
// Handle setting up cluster auth and return clientset
func auth() *kubernetes.Clientset {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
config, err := kubeConfig.ClientConfig()
if err != nil {
panic(err.Error())
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
return clientset
}
================================================
FILE: cmd/collect.go
================================================
package cmd
import (
"context"
"log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
func collect(clientset *kubernetes.Clientset, namespace []string, labels []string) {
if limitFlag < 0 {
log.Fatal("FATAL CONFIGURATION: --limit flag value must not be negative.")
}
if !(output == "standard" || output == "yaml" || output == "json") {
log.Fatal("FATAL CONFIGURATION: --output flag can only be: standard, json, yaml")
}
for _, ns := range namespace {
for _, lb := range labels {
pods, err := clientset.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: lb})
if err != nil {
log.Fatal(err.Error())
}
for _, v := range pods.Items {
initializeContainerMap(v.ObjectMeta.Name, v.ObjectMeta.Namespace)
restarts := int32(0)
for _, vv := range v.Status.ContainerStatuses {
restarts += vv.RestartCount
trackContainers(v.ObjectMeta.Name, v.ObjectMeta.Namespace, vv.Name, vv.RestartCount)
}
trackPods(v.ObjectMeta.Name, v.ObjectMeta.Namespace, restarts)
trackNamespaces(v.ObjectMeta.Namespace, restarts)
trackLabels(labels, v.ObjectMeta.Labels, restarts)
trackNodes(v.Spec.NodeName, restarts)
}
}
}
showResults()
}
func trackNamespaces(namespace string, count int32) {
namespaceTracker[namespace] += count
}
func trackNodes(node string, count int32) {
nodeTracker[node] += count
}
func trackPods(pod, namespace string, count int32) {
podTracker[namespace+":"+pod] = count
}
func trackContainers(pod, namespace, container string, count int32) {
containerTracker[namespace+":"+pod][container] = count
}
func initializeContainerMap(pod, namespace string) {
containerTracker[namespace+":"+pod] = make(map[string]int32)
}
// plabels = Pod Labels
// tlabels = (User-defined) tracking labels
func trackLabels(tlabels []string, plabels map[string]string, count int32) {
// range through all the labels specified in the -l CLI flag
for _, l := range tlabels {
// range through plabels to see if any match the user specified labels. If so, add it to the map
// the default value "*" will match everything
for k, v := range plabels {
if l == k || l == "" {
labelTracker[k+":"+v] += count
}
}
}
}
================================================
FILE: cmd/collect_test.go
================================================
package cmd
import (
"testing"
)
func TestTrackNamespaces(t *testing.T) {
trackNamespaces("ns1", 5)
trackNamespaces("ns1", 2)
if namespaceTracker["ns1"] != int32(7) {
t.Errorf("ns1 namespace expected to have a count of 7 but instead shows: %v", namespaceTracker["ns1"])
}
}
func TestTrackNodes(t *testing.T) {
trackNodes("node01", 5)
trackNodes("node01", 2)
trackNodes("node02", 2)
if nodeTracker["node01"] != int32(7) {
t.Errorf("node01 node expected to have a count of 7 but instead shows: %v", nodeTracker["node01"])
}
}
func TestTrackPods(t *testing.T) {
// Test that a pod with the same name in a different namespace is held uniquely in the map
trackPods("pod1", "default", 3)
trackPods("pod1", "other", 2)
if podTracker["default:pod1"] != 3 {
t.Errorf("pod1 pod expected to have a count of 3 but instead shows: %v", podTracker["pod1"])
}
}
func TestTrackContainers(t *testing.T) {
initializeContainerMap("pod1", "default")
trackContainers("pod1", "default", "container1", 5)
if containerTracker["default:pod1"]["container1"] != 5 {
t.Errorf("pod1/container1 expected to have a count of 5 but instead shows: %v", containerTracker)
}
}
func TestTrackLabels(t *testing.T) {
tlabels := []string{"app", "k8s-app"}
plabelsA := map[string]string{
"app": "app1",
"other": "label",
}
plabelsB := map[string]string{
"k8s-app": "app2",
}
trackLabels(tlabels, plabelsA, 3)
trackLabels(tlabels, plabelsB, 5)
// other:label should not exist because it is not defined in tlabels
if labelTracker["other:label"] != int32(0) {
t.Errorf("other:label should not exist because it was not defined in user-defined tlabels")
}
if labelTracker["app:app1"] != int32(3) {
t.Errorf("app:app1 should be equal to 3 since it is defined in the tlabels but instead shows: %v", labelTracker["app:app1"])
}
if labelTracker["k8s-app:app2"] != int32(5) {
t.Errorf("ks-app:app2 should be equal to 5 since it is defined in the tlabels but instead shows: %v", labelTracker["k8s-app:app2"])
}
}
================================================
FILE: cmd/commands.go
================================================
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"kurt/internal/version"
)
var cmdNamespaces = &cobra.Command{
Use: "namespaces",
Short: "Only print namespace-wide restart counts",
Long: "Only print namespace-wide restart counts",
Aliases: []string{"ns"},
Run: func(cmd *cobra.Command, args []string) {
printNS = true
printAll = false
clientset := auth()
collect(clientset, inamespace, ilabels)
},
}
var cmdNodes = &cobra.Command{
Use: "nodes",
Short: "Only print node restart counts",
Long: "Only print node restart counts",
Aliases: []string{"no", "node"},
Run: func(cmd *cobra.Command, args []string) {
printNode = true
printAll = false
clientset := auth()
collect(clientset, inamespace, ilabels)
},
}
var cmdPods = &cobra.Command{
Use: "pods",
Short: "Only print pod restart counts",
Long: "Only print pod restart counts",
Aliases: []string{"po"},
Run: func(cmd *cobra.Command, args []string) {
printPods = true
printAll = false
clientset := auth()
collect(clientset, inamespace, ilabels)
},
}
var cmdLabels = &cobra.Command{
Use: "labels",
Short: "Only print restart counts grouped by labels",
Long: "Only print restart counts grouped by labels",
Run: func(cmd *cobra.Command, args []string) {
printLabel = true
printAll = false
clientset := auth()
collect(clientset, inamespace, ilabels)
},
}
var cmdAll = &cobra.Command{
Use: "all",
Short: "Print all groupings collected by kurt!",
Long: "Print all groupings collected by kurt!",
Run: func(cmd *cobra.Command, args []string) {
printAll = true
clientset := auth()
collect(clientset, inamespace, ilabels)
},
}
var cmdVersion = &cobra.Command{
Use: "version",
Short: "Print the current version and exit",
Long: `Print the current version and exit`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("kurt: %s\n", version.Version())
},
}
func init() {
rootCmd.AddCommand(cmdNamespaces)
rootCmd.AddCommand(cmdNodes)
rootCmd.AddCommand(cmdPods)
rootCmd.AddCommand(cmdLabels)
rootCmd.AddCommand(cmdAll)
rootCmd.AddCommand(cmdVersion)
}
================================================
FILE: cmd/constants.go
================================================
package cmd
var namespaceTracker = make(map[string]int32)
var nodeTracker = make(map[string]int32)
var podTracker = make(map[string]int32)
var labelTracker = make(map[string]int32)
var containerTracker = make(map[string]map[string]int32)
var printAll bool
var printNS bool
var printNode bool
var printPods bool
var printLabel bool
================================================
FILE: cmd/printer.go
================================================
package cmd
import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"gopkg.in/yaml.v2"
)
type StructuredOutput struct {
Namespaces ItemList `yaml:"namespaces,omitempty" json:"namespaces,omitempty"`
Nodes ItemList `yaml:"nodes,omitempty" json:"nodes,omitempty"`
Labels ItemList `yaml:"labels,omitempty" json:"labels,omitempty"`
Pods ItemList `yaml:"pods,omitempty" json:"pods,omitempty"`
}
func showResults() {
var so StructuredOutput
w := new(tabwriter.Writer)
// minwidth, tabwidth, padding, padchar, flags
w.Init(os.Stdout, 8, 8, 1, '\t', 0)
if output == "standard" {
fmt.Printf("kurt: KUbernetes Restart Tracker")
}
if printNS || printAll {
so.Namespaces = returnSortedLimit(namespaceTracker, limitFlag, false, nil)
if output == "standard" {
fmt.Println("\n\n==========")
fmt.Fprintf(w, "\n Namespace\tRestarts\t")
fmt.Fprintf(w, "\n \t\t")
for _, v := range so.Namespaces {
fmt.Fprintf(w, "\n %v\t%v\t", v.Name, v.Count)
}
w.Flush()
}
}
if printNode || printAll {
so.Nodes = returnSortedLimit(nodeTracker, limitFlag, false, nil)
if output == "standard" {
fmt.Println("\n\n==========")
fmt.Fprintf(w, "\n Node\tRestarts\t")
fmt.Fprintf(w, "\n \t\t")
for _, v := range so.Nodes {
fmt.Fprintf(w, "\n %v\t%v\t", v.Name, v.Count)
}
w.Flush()
}
}
if printLabel || printAll {
if len(labelTracker) > 0 {
so.Labels = returnSortedLimit(labelTracker, limitFlag, false, nil)
if output == "standard" {
fmt.Println("\n\n==========")
fmt.Fprintf(w, "\n Label\tRestarts\t")
fmt.Fprintf(w, "\n \t\t")
for _, v := range so.Labels {
fmt.Fprintf(w, "\n %v\t%v\t", v.Name, v.Count)
}
w.Flush()
}
}
}
if printPods || printAll {
so.Pods = returnSortedLimit(podTracker, limitFlag, true, containerTracker)
if output == "standard" {
fmt.Println("\n\n==========")
fmt.Fprintf(w, "\n Pod\tNamespace\tRestarts\t")
fmt.Fprintf(w, "\n \t\t\t")
for _, v := range so.Pods {
fmt.Fprintf(w, "\n %v\t%v\t%v\t", v.Name, v.Namespace, v.Count)
if v.Containers != nil && ishowContainers {
for _, vv := range v.Containers {
fmt.Fprintf(w, "\n └%v: %v\t\t\t", vv.Name, vv.Count)
}
}
}
w.Flush()
}
}
switch output {
case "json":
j, _ := json.MarshalIndent(so, "", " ")
fmt.Println(string(j))
case "yaml":
y, _ := yaml.Marshal(so)
fmt.Println(string(y))
default:
fmt.Printf("\n")
}
}
// sorting results
// https://stackoverflow.com/a/18695740
type Container struct {
Name string `yaml:"name" json:"name"`
Count int32 `yaml:"count" json:"count"`
}
type Containers []Container
func (p Containers) Len() int { return len(p) }
func (p Containers) Less(i, j int) bool { return p[i].Count < p[j].Count }
func (p Containers) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type Item struct {
Name string `yaml:"name" json:"name"`
Count int32 `yaml:"count" json:"count"`
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
Containers Containers `yaml:"containers,omitempty" json:"containers,omitempty"`
}
type ItemList []Item
func (p ItemList) Len() int { return len(p) }
func (p ItemList) Less(i, j int) bool { return p[i].Count < p[j].Count }
func (p ItemList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func returnSortedLimit(data map[string]int32, limit int, parseNS bool, containers map[string]map[string]int32) ItemList {
il := make(ItemList, len(data))
i := 0
for k, v := range data {
if parseNS {
// split the Name so we can display the pod an namespace separately
// only used for pod items
s := strings.Split(k, ":")
il[i] = Item{s[1], v, s[0], createContainerSlice(containers[k])}
} else {
il[i] = Item{k, v, "", nil}
}
i++
}
sort.Sort(sort.Reverse(il))
if len(il) <= limit || limit == 0 {
return il
} else {
return il[0:limit]
}
}
func createContainerSlice(containers map[string]int32) []Container {
if containers != nil {
c := make(Containers, len(containers))
i := 0
for k, v := range containers {
c[i] = Container{k, v}
i++
}
sort.Sort(sort.Reverse(c))
return c
}
return nil
}
================================================
FILE: cmd/printer_test.go
================================================
package cmd
import (
"reflect"
"testing"
)
func Test_returnSortedLimit(t *testing.T) {
type args struct {
data map[string]int32
limit int
parseNS bool
containers map[string]map[string]int32
}
tests := []struct {
name string
args args
want ItemList
}{
{
name: "test1",
args: args{
data: map[string]int32{
"test1": 5,
"test2": 7,
"test3": 8,
"test4": 9,
"test5": 0,
"test6": 4,
},
limit: 5,
parseNS: false,
containers: nil,
},
want: ItemList{
Item{
Name: "test4",
Count: 9,
Namespace: "",
Containers: nil,
},
Item{
Name: "test3",
Count: 8,
Namespace: "",
Containers: nil,
},
Item{
Name: "test2",
Count: 7,
Namespace: "",
Containers: nil,
},
Item{
Name: "test1",
Count: 5,
Namespace: "",
Containers: nil,
},
Item{
Name: "test6",
Count: 4,
Namespace: "",
Containers: nil,
},
},
},
{
name: "test2",
args: args{
data: map[string]int32{
"test1:pod1": 5,
"test2:pod2": 7,
"test2:pod3": 8,
"test1:pod4": 9,
"test2:pod5": 0,
"test1:pod6": 4,
},
limit: 5,
parseNS: true,
containers: map[string]map[string]int32{
"test1:pod1": {"container1": 5},
"test2:pod2": {"container1": 7},
"test2:pod3": {"container1": 8},
"test1:pod4": {"container3": 2, "container1": 4, "container2": 3},
"test2:pod5": {"container1": 0},
"test1:pod6": {"container1": 4},
},
},
want: ItemList{
Item{
Name: "pod4",
Count: 9,
Namespace: "test1",
Containers: Containers{Container{"container1", 4}, Container{"container2", 3}, Container{"container3", 2}},
},
Item{
Name: "pod3",
Count: 8,
Namespace: "test2",
Containers: Containers{Container{"container1", 8}},
},
Item{
Name: "pod2",
Count: 7,
Namespace: "test2",
Containers: Containers{Container{"container1", 7}},
},
Item{
Name: "pod1",
Count: 5,
Namespace: "test1",
Containers: Containers{Container{"container1", 5}},
},
Item{
Name: "pod6",
Count: 4,
Namespace: "test1",
Containers: Containers{Container{"container1", 4}},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := returnSortedLimit(tt.args.data, tt.args.limit, tt.args.parseNS, tt.args.containers); !reflect.DeepEqual(got, tt.want) {
t.Errorf("returnSortedLimit() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: cmd/root.go
================================================
package cmd
import (
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var inamespace []string
var ilabels []string
var ishowContainers bool
var limitFlag int
var output string
var rootCmd = &cobra.Command{
Use: "kurt",
Short: "KUbernetes Restart Tracker",
Long: `kurt: KUbernetes Restart Tracker
A restart tracker that gives context to what is restarting in your cluster
`,
}
func init() {
rootCmd.PersistentFlags().StringSliceVarP(&inamespace, "namespace", "n", []string{""}, "Specify namespace for kurt to collect restart metrics.\nLeave blank to collect in all namespaces.")
rootCmd.PersistentFlags().StringSliceVarP(&ilabels, "label", "l", []string{""}, "Specify multiple times for the label keys you want to see.\nFor example: \"kurt all -l app\"")
rootCmd.PersistentFlags().IntVarP(&limitFlag, "limit", "c", 5, "Limit the number of resources you want to see. Set limit to 0 for no limits. Must be positive.\nFor example: \"kurt all -c=10\"")
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "standard", "Specify output type. Options are: json, yaml, standard\nFor example: \"kurt all -o json\"")
// command specific flags
cmdPods.PersistentFlags().BoolVarP(&ishowContainers, "show-containers", "", false, "Show specific container restart counts for pods\nFor example: \"kurt pods --show-containers\"")
cmdAll.PersistentFlags().BoolVarP(&ishowContainers, "show-containers", "", false, "Show specific container restart counts for pods\nFor example: \"kurt pods --show-containers\"")
if strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-") {
rootCmd.SetUsageTemplate(strings.NewReplacer(
"{{.UseLine}}", "kubectl {{.UseLine}}",
"{{.CommandPath}}", "kubectl {{.CommandPath}}").Replace(rootCmd.UsageTemplate()))
}
}
func Execute() {
cobra.CheckErr(rootCmd.Execute())
}
================================================
FILE: go.mod
================================================
module kurt
go 1.23.0
toolchain go1.23.4
require (
github.com/spf13/cobra v1.8.1
gopkg.in/yaml.v2 v2.4.0
k8s.io/apimachinery v0.32.1
k8s.io/client-go v0.32.1
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/protobuf v1.36.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.32.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
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.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
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/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=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU=
k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg=
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-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk=
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
================================================
FILE: internal/version/version.go
================================================
package version
import (
"fmt"
"runtime"
)
// NOTE: these variables are injected at build time
var (
version string = "development"
gitSHA, buildTime string
build Build
)
type Build struct {
Version string `json:"version,omitempty"`
GitSHA string `json:"git,omitempty"`
BuildTime string `json:"buildTime,omitempty"`
GoVersion string `json:"goversion,omitempty"`
}
func initBuild() {
build.Version = version
if len(gitSHA) >= 7 {
build.GitSHA = gitSHA[:7]
}
build.BuildTime = buildTime
build.GoVersion = runtime.Version()
}
func Version() string {
initBuild()
return fmt.Sprintf("%s\n%s", build.Version, build)
}
================================================
FILE: main.go
================================================
package main
import (
"kurt/cmd"
)
func main() {
cmd.Execute()
}
================================================
FILE: tests/e2e.sh
================================================
#!/bin/bash
set -eou pipefail
kubectl apply -f tests/test.yml --wait
kubectl wait --for=condition=Ready pod/nginx -n test1
kubectl wait --for=condition=Ready pod/apache -n test2
# Generate some pod restarts
kubectl exec nginx -n test1 -- bash -c "kill 1"
kubectl exec apache -n test2 -- bash -c "kill 1"
sleep 5
kubectl exec nginx -n test1 -- bash -c "kill 1"
echo "[!] wait for pods to finish restarting..."
sleep 30
NGINX_RESTARTS=$(./kurt pods -n test1 -o json | jq '.pods[0].count')
if [ $NGINX_RESTARTS -eq 2 ]; then
echo "[+] Correct number of restarts for nginx 👍"
else
echo "[!] Incorrect number of restarts for nginx: $NGINX_RESTARTS"
exit 1
fi
APACHE_RESTARTS=$(./kurt pods -n test2 -o json | jq '.pods[0].count')
if [ $APACHE_RESTARTS -eq 1 ]; then
echo "[+] Correct number of restarts for apache 👍"
else
echo "[!] Incorrect number of restarts for apache: $APACHE_RESTARTS"
exit 1
fi
echo "[+] Cleaning up..."
kubectl delete -f tests/test.yml
================================================
FILE: tests/test.yml
================================================
kind: Namespace
apiVersion: v1
metadata:
name: test1
---
kind: Namespace
apiVersion: v1
metadata:
name: test2
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: test1
spec:
containers:
- name: nginx
image: nginx:latest
---
apiVersion: v1
kind: Pod
metadata:
name: apache
namespace: test2
spec:
containers:
- name: apache
image: httpd:latest
gitextract_tkrgsaxg/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ ├── e2e.yml
│ └── go-releaser.yml
├── .gitignore
├── .goreleaser.yml
├── .krew.yaml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd/
│ ├── auth.go
│ ├── collect.go
│ ├── collect_test.go
│ ├── commands.go
│ ├── constants.go
│ ├── printer.go
│ ├── printer_test.go
│ └── root.go
├── go.mod
├── go.sum
├── internal/
│ └── version/
│ └── version.go
├── main.go
└── tests/
├── e2e.sh
└── test.yml
SYMBOL INDEX (35 symbols across 9 files)
FILE: cmd/auth.go
function auth (line 10) | func auth() *kubernetes.Clientset {
FILE: cmd/collect.go
function collect (line 11) | func collect(clientset *kubernetes.Clientset, namespace []string, labels...
function trackNamespaces (line 46) | func trackNamespaces(namespace string, count int32) {
function trackNodes (line 50) | func trackNodes(node string, count int32) {
function trackPods (line 54) | func trackPods(pod, namespace string, count int32) {
function trackContainers (line 58) | func trackContainers(pod, namespace, container string, count int32) {
function initializeContainerMap (line 62) | func initializeContainerMap(pod, namespace string) {
function trackLabels (line 68) | func trackLabels(tlabels []string, plabels map[string]string, count int3...
FILE: cmd/collect_test.go
function TestTrackNamespaces (line 7) | func TestTrackNamespaces(t *testing.T) {
function TestTrackNodes (line 16) | func TestTrackNodes(t *testing.T) {
function TestTrackPods (line 26) | func TestTrackPods(t *testing.T) {
function TestTrackContainers (line 35) | func TestTrackContainers(t *testing.T) {
function TestTrackLabels (line 43) | func TestTrackLabels(t *testing.T) {
FILE: cmd/commands.go
function init (line 80) | func init() {
FILE: cmd/printer.go
type StructuredOutput (line 14) | type StructuredOutput struct
function showResults (line 21) | func showResults() {
type Container (line 104) | type Container struct
type Containers (line 109) | type Containers
method Len (line 111) | func (p Containers) Len() int { return len(p) }
method Less (line 112) | func (p Containers) Less(i, j int) bool { return p[i].Count < p[j].Cou...
method Swap (line 113) | func (p Containers) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type Item (line 115) | type Item struct
type ItemList (line 122) | type ItemList
method Len (line 124) | func (p ItemList) Len() int { return len(p) }
method Less (line 125) | func (p ItemList) Less(i, j int) bool { return p[i].Count < p[j].Count }
method Swap (line 126) | func (p ItemList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
function returnSortedLimit (line 128) | func returnSortedLimit(data map[string]int32, limit int, parseNS bool, c...
function createContainerSlice (line 150) | func createContainerSlice(containers map[string]int32) []Container {
FILE: cmd/printer_test.go
function Test_returnSortedLimit (line 8) | func Test_returnSortedLimit(t *testing.T) {
FILE: cmd/root.go
function init (line 26) | func init() {
function Execute (line 44) | func Execute() {
FILE: internal/version/version.go
type Build (line 15) | type Build struct
function initBuild (line 22) | func initBuild() {
function Version (line 31) | func Version() string {
FILE: main.go
function main (line 7) | func main() {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (60K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 660,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: kwsorensen\n\n---\n\n**Descr"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 612,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: kwsorensen\n\n-"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 622,
"preview": "<!-- Thanks for sending a pull request! Here are some tips for you:\n\n1. Please file an issue before making a pull requ"
},
{
"path": ".github/dependabot.yml",
"chars": 503,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/ci.yml",
"chars": 864,
"preview": "on:\n pull_request:\n branches:\n - main\n push:\n branches:\n - \"main\"\n tags:\n - \"v*.*.*\"\n\njobs:\n "
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 2318,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".github/workflows/e2e.yml",
"chars": 1623,
"preview": "name: e2e tests\non:\n pull_request:\n workflow_dispatch:\njobs:\n run-e2e-tests:\n runs-on: ubuntu-latest\n strategy:"
},
{
"path": ".github/workflows/go-releaser.yml",
"chars": 772,
"preview": "name: goreleaser\n\non:\n push:\n tags:\n - \"v*.*.*\"\n\njobs:\n goreleaser:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".gitignore",
"chars": 227,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n.vscode/\n**/__debug_bin\nkurt\n!kurt/\n\n# Test binary, "
},
{
"path": ".goreleaser.yml",
"chars": 645,
"preview": "builds:\n - id: kurt\n goos:\n - darwin\n - linux\n - windows\n goarch:\n - amd64\n - arm64\n "
},
{
"path": ".krew.yaml",
"chars": 2009,
"preview": "apiVersion: krew.googlecontainertools.github.com/v1alpha2\nkind: Plugin\nmetadata:\n name: kurt\nspec:\n version: {{ .TagNa"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5288,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 47,
"preview": "# Contributing\n\nCreate an Issue or Create a PR\n"
},
{
"path": "LICENSE",
"chars": 1084,
"preview": "MIT License\n\nCopyright (c) 2021 Kyle Sorensen, Aaron Stults\n\nPermission is hereby granted, free of charge, to any person"
},
{
"path": "README.md",
"chars": 3485,
"preview": "# kurt\n```\nkurt: KUbernetes Restart Tracker\n\nA restart tracker that gives context to what is restarting in your cluster\n"
},
{
"path": "cmd/auth.go",
"chars": 642,
"preview": "package cmd\n\nimport (\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/too"
},
{
"path": "cmd/collect.go",
"chars": 2245,
"preview": "package cmd\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)"
},
{
"path": "cmd/collect_test.go",
"chars": 2027,
"preview": "package cmd\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTrackNamespaces(t *testing.T) {\n\ttrackNamespaces(\"ns1\", 5)\n\ttrackNamespaces"
},
{
"path": "cmd/commands.go",
"chars": 2124,
"preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"github.com/spf13/cobra\"\n\t\"kurt/internal/version\"\n)\n\nvar cmdNamespaces = &cobra.Command{\n\t"
},
{
"path": "cmd/constants.go",
"chars": 333,
"preview": "package cmd\n\nvar namespaceTracker = make(map[string]int32)\nvar nodeTracker = make(map[string]int32)\nvar podTracker = mak"
},
{
"path": "cmd/printer.go",
"chars": 4222,
"preview": "package cmd\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype St"
},
{
"path": "cmd/printer_test.go",
"chars": 2742,
"preview": "package cmd\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc Test_returnSortedLimit(t *testing.T) {\n\ttype args struct {\n\t\tdata "
},
{
"path": "cmd/root.go",
"chars": 1832,
"preview": "package cmd\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar inamespace []string\nvar ilabel"
},
{
"path": "go.mod",
"chars": 2143,
"preview": "module kurt\n\ngo 1.23.0\n\ntoolchain go1.23.4\n\nrequire (\n\tgithub.com/spf13/cobra v1.8.1\n\tgopkg.in/yaml.v2 v2.4.0\n\tk8s.io/ap"
},
{
"path": "go.sum",
"chars": 13642,
"preview": "github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/davecgh/go-spe"
},
{
"path": "internal/version/version.go",
"chars": 662,
"preview": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\n// NOTE: these variables are injected at build time\nvar (\n\tversion "
},
{
"path": "main.go",
"chars": 69,
"preview": "package main\n\nimport (\n\t\"kurt/cmd\"\n)\n\nfunc main() {\n\tcmd.Execute()\n}\n"
},
{
"path": "tests/e2e.sh",
"chars": 985,
"preview": "#!/bin/bash\nset -eou pipefail\n\nkubectl apply -f tests/test.yml --wait\nkubectl wait --for=condition=Ready pod/nginx -n te"
},
{
"path": "tests/test.yml",
"chars": 379,
"preview": "kind: Namespace\napiVersion: v1\nmetadata:\n name: test1\n---\nkind: Namespace\napiVersion: v1\nmetadata:\n name: test2\n---\nap"
}
]
About this extraction
This page contains the full source code of the soraro/kurt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (53.5 KB), approximately 19.7k tokens, and a symbol index with 35 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.