Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dhenkel92 committed Oct 25, 2022
1 parent 59ac8fb commit 35f2cbe
Show file tree
Hide file tree
Showing 8 changed files with 1,282 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
# - id: check-added-large-files
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/dnephin/pre-commit-golang.git
rev: v0.3.5
hooks:
- id: go-fmt
- id: go-imports
# todo: hook does not support multiple binaries in one repo
# https://github.com/dnephin/pre-commit-golang/issues/30
# - id: go-vet
# - id: go-lint
- id: go-cyclo
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ARGS=

.PHONY: run
run:
go run ./cmd/ cover ${ARGS}
25 changes: 25 additions & 0 deletions cmd/kubectl-pdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"os"

"github.com/dhenkel92/kubectl-pdb/pkg/cmd"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

func main() {
flags := pflag.NewFlagSet("kubectl-pdb", pflag.ExitOnError)
pflag.CommandLine = flags

rootCmd := &cobra.Command{
Use: "pdb",
Short: "Utility to work with pod disruption budgets",
}
rootCmd.AddCommand(cmd.NewCmdPdb(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}))

if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
68 changes: 68 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module github.com/dhenkel92/kubectl-pdb

go 1.18

require (
github.com/spf13/cobra v1.6.0
github.com/spf13/pflag v1.0.5
k8s.io/apimachinery v0.23.13
k8s.io/cli-runtime v0.23.13
k8s.io/client-go v0.23.13
)

require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/imdario/mergo v0.3.5 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.23.13 // indirect
k8s.io/klog/v2 v2.70.1 // indirect
// https://github.com/kubernetes/client-go/issues/1075
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.10.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
854 changes: 854 additions & 0 deletions go.sum

Large diffs are not rendered by default.

181 changes: 181 additions & 0 deletions pkg/cmd/pdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package cmd

import (
"fmt"
"strings"

"github.com/dhenkel92/kubectl-pdb/pkg/kube"
"github.com/liggitt/tabwriter"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
)

type PDBOptions struct {
genericclioptions.IOStreams

configFlags *genericclioptions.ConfigFlags
AllNamespaces bool
Namespace string
PodName string
Output string
Template string
}

func NewPDBOptions(streams genericclioptions.IOStreams) *PDBOptions {
return &PDBOptions{
configFlags: genericclioptions.NewConfigFlags(true),
IOStreams: streams,
}
}

func (o *PDBOptions) GetPrinter() (printers.ResourcePrinter, error) {
switch o.Output {
case "jsonpath":
return printers.NewJSONPathPrinter(o.Template)
case "json":
return &printers.JSONPrinter{}, nil
case "yaml":
return &printers.YAMLPrinter{}, nil
}
return printers.NewTablePrinter(printers.PrintOptions{
WithKind: false,
NoHeaders: false,
Wide: false,
WithNamespace: o.AllNamespaces,
ShowLabels: false,
ColumnLabels: []string{},
}), nil
}

func (o *PDBOptions) GetNamespace() string {
if o.AllNamespaces {
return ""
}
return o.Namespace
}

func (o *PDBOptions) Complete(cmd *cobra.Command, args []string) error {
var err error

o.Namespace, _, err = o.configFlags.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}

if len(args) > 0 && args[0] != "" {
o.PodName = args[0]
}

if strings.HasPrefix(o.Output, "jsonpath") {
split := strings.Split(o.Output, "=")
o.Output = split[0]
o.Template = split[1]
}
return nil
}

func (o *PDBOptions) Validate() error {
switch o.Output {
case "json", "yaml", "human", "jsonpath":
default:
return fmt.Errorf("invalid output '%s'", o.Output)
}
return nil
}

func (o *PDBOptions) getWriter() *tabwriter.Writer {
return printers.GetNewTabWriter(o.Out)
}

func (o *PDBOptions) Run() error {
printer, err := o.GetPrinter()
if err != nil {
return err
}

kubeClients, err := kube.New(o.configFlags)
if err != nil {
return err
}

pods, err := kubeClients.GetNamespacedPods(o.GetNamespace(), o.PodName)
if err != nil {
return err
}

pdbs, err := kubeClients.GetNamespacedPDBs(o.GetNamespace())
if err != nil {
return err
}

entries := make([]PodPDBEntry, 0, len(pods))
for ns, podLst := range pods {
podPDBs, ok := pdbs[ns]
if !ok {
continue
}

for _, pod := range podLst {
pdbs := getMatchingPDBs(&pod, podPDBs)
entries = append(entries, PodPDBEntry{
Pod: pod,
Pdbs: pdbs,
})
}
}

lst := &PodPDBList{Items: entries}
w := o.getWriter()
if err := printer.PrintObj(lst.toMetaTable(), w); err != nil {
return err
}
return w.Flush()
}

func NewCmdPdb(streams genericclioptions.IOStreams) *cobra.Command {
o := NewPDBOptions(streams)

cmd := &cobra.Command{
Use: "cover [pod name]",
Short: "Shows which PDBs are covering the given workload.",
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(cmd, args); err != nil {
return err
}
if err := o.Validate(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
}

return nil
},
}

flags := cmd.Flags()
flags.BoolVarP(&o.AllNamespaces, "all-namespaces", "a", false, "")
flags.StringVarP(&o.Output, "output", "o", "human", "[human, json, yaml, jsonpath]")
o.configFlags.AddFlags(flags)

return cmd
}

func getMatchingPDBs(pod *corev1.Pod, pdbs []policyv1.PodDisruptionBudget) []policyv1.PodDisruptionBudget {
ls := labels.Set(pod.Labels)

matchingPDBs := make([]policyv1.PodDisruptionBudget, 0)
for _, pdb := range pdbs {
// TODO catch error
selector, _ := metav1.LabelSelectorAsSelector(pdb.Spec.Selector)
if selector.Matches(ls) {
matchingPDBs = append(matchingPDBs, pdb)
}
}
return matchingPDBs
}
62 changes: 62 additions & 0 deletions pkg/cmd/pod_pdb_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cmd

import (
"strings"

corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

type PodPDBEntry struct {
Pod corev1.Pod
Pdbs []policyv1.PodDisruptionBudget
}

func (e *PodPDBEntry) getPDBList() string {
names := make([]string, 0, len(e.Pdbs))
for _, pdb := range e.Pdbs {
names = append(names, pdb.GetName())
}
return strings.Join(names, ", ")
}

func (e *PodPDBEntry) OwnerName() string {
for _, owner := range e.Pod.GetOwnerReferences() {
return owner.Kind
}
return ""
}

type PodPDBList struct {
Items []PodPDBEntry
}

func (lst *PodPDBList) toMetaTable() runtime.Object {
rows := make([]metav1.TableRow, 0, len(lst.Items))
for _, entry := range lst.Items {
rows = append(rows, metav1.TableRow{
Cells: []interface{}{
entry.OwnerName(),
entry.Pod.GetName(),
len(entry.Pdbs),
entry.getPDBList(),
},
Object: runtime.RawExtension{
Object: entry.Pod.DeepCopy(),
},
})
}

return &metav1.Table{
TypeMeta: metav1.TypeMeta{Kind: "pod", APIVersion: "v1"},
ColumnDefinitions: []metav1.TableColumnDefinition{
{Name: "owner", Type: "string", Format: "name", Description: "owner"},
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "CNT", Type: "integer", Description: "Amount of pdbs"},
{Name: "PDBs", Type: "string", Description: "hello"},
},
Rows: rows,
}
}
Loading

0 comments on commit 35f2cbe

Please sign in to comment.