-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
1,282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
ARGS= | ||
|
||
.PHONY: run | ||
run: | ||
go run ./cmd/ cover ${ARGS} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
Oops, something went wrong.