Skip to content

Commit

Permalink
Create evict binary (#7)
Browse files Browse the repository at this point in the history
* feature: create evict pod binary
  • Loading branch information
dhenkel92 authored Dec 6, 2023
1 parent 59ebf7f commit 66deeab
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bin
.DS_Store

dist/
48 changes: 47 additions & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,25 @@ builds:
- linux
- windows
- darwin
- env:
- CGO_ENABLED=0
main: ./cmd/kubectl-evict/kubectl-evict.go
id: kubectl-evict
binary: kubectl-evict
goarch:
- amd64
- arm
- arm64
goos:
- linux
- windows
- darwin

archives:
- format: tar.gz
- id: kubectl-pdb
builds:
- kubectl-pdb
format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
Expand All @@ -42,8 +58,38 @@ archives:
format_overrides:
- goos: windows
format: zip
- id: kubectl-evict
builds:
- kubectl-evict
format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .Binary }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
brews:
- name: kubectl-pdb
ids:
- kubectl-pdb
folder: Formula
homepage: "https://github.com/dhenkel92/kubectl-pdb"
license: "MIT"
repository:
owner: dhenkel92
name: homebrew-tap
branch: main
pull_request:
enabled: true
- name: kubectl-evict
ids:
- kubectl-evict
folder: Formula
homepage: "https://github.com/dhenkel92/kubectl-pdb"
license: "MIT"
Expand Down
51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<!-- <img src="images/logo.png" alt="Logo" width="80" height="80"> -->
<!-- </a> -->

<h3 align="center">Kubectl PDB plugin</h3>
<h3 align="center">Kubectl PDB & Eviction plugin</h3>

<p align="center">
A kubectl plugin to work with pod disruption budgets.
Expand Down Expand Up @@ -37,6 +37,7 @@ This plugin aims to address this issue with the following key features:
- Lists all PDBs matching a given workload.
- Lists all workload pods matching a given PDB.
- Creates new PDBs from the command line.
- Evict a workload from a node.

## Getting Started

Expand All @@ -49,11 +50,13 @@ To get a local copy up and running follow these simple example steps.
You can install the tool via Homebrew and the tap repository can be found [here.](https://github.com/dhenkel92/homebrew-tap)
```
brew install dhenkel92/homebrew-tap/kubectl-pdb
brew install dhenkel92/homebrew-tap/kubectl-evict
```

In order to get a newer version, just upgrade via Homebrew
```
brew upgrade kubectl-pdb
brew upgrade kubectl-evict
```

### Other distributions
Expand All @@ -62,6 +65,8 @@ See the [Releases page](https://github.com/dhenkel92/kubectl-pdb/releases) for a

## Usage

### Kubectl pdb

```
Utility to work with pod disruption budgets
Expand Down Expand Up @@ -114,6 +119,50 @@ Create new PDB:
kubectl pdb create <resource_type>/<resource_name> --dry-run -o yaml
```

### Kubectl evict

```
Utility to evict a pod from a node
Usage:
evict pod [flags]
Flags:
--dry-run If true, only print the object that would be sent, without sending it. (default true)
-h, --help help for pod
-o, --output string Output format. One of: json|yaml (default "json")
Global Flags:
--as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
--as-uid string UID to impersonate for the operation.
--cache-dir string Default cache directory (default "/Users/daniel.henkel/.kube/cache")
--certificate-authority string Path to a cert file for the certificate authority
--client-certificate string Path to a client certificate file for TLS
--client-key string Path to a client key file for TLS
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--disable-compression If true, opt-out of response compression for all requests to the server
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
-n, --namespace string If present, the namespace scope for this CLI request
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
-s, --server string The address and port of the Kubernetes API server
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
--token string Bearer token for authentication to the API server
--user string The name of the kubeconfig user to use
```

Evict a pod in dry-run mode (Default: true):
```
kubectl evict pod [-n <namespace>] <pod_name>
```

Trigger real pod eviction:
```
kubectl evict pod [-n <namespace>] <pod_name> --dry-run=false
```

## Contributing

### Creating A Pull Request
Expand Down
30 changes: 30 additions & 0 deletions cmd/kubectl-evict/kubectl-evict.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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-evict", pflag.ExitOnError)
pflag.CommandLine = flags

conf := genericclioptions.NewConfigFlags(true)
conf.AddFlags(flags)

streams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}

rootCmd := &cobra.Command{
Use: "evict",
Short: "Utility to evict a pod from a node",
}
rootCmd.AddCommand(cmd.NewCmdEvictPod(streams, conf))

if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
139 changes: 139 additions & 0 deletions pkg/cmd/evict_pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package cmd

import (
"context"
"fmt"

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

type EvictPodOptions struct {
genericclioptions.IOStreams

configFlags *genericclioptions.ConfigFlags
Namespace string
PodName string
DryRun bool
Output string
}

func NewEvictPodOptions(streams genericclioptions.IOStreams, configFlags *genericclioptions.ConfigFlags) *EvictPodOptions {
return &EvictPodOptions{
IOStreams: streams,
configFlags: configFlags,
}
}

func NewCmdEvictPod(streams genericclioptions.IOStreams, configFlags *genericclioptions.ConfigFlags) *cobra.Command {
o := NewEvictPodOptions(streams, configFlags)

cmd := &cobra.Command{
Use: "pod <pod_name>",
Short: "Utility to evict a pod from a node",
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 utils.HandleRunError(streams, err)
}
return nil
},
}

cmd.Flags().StringVarP(&o.Output, "output", "o", "json", "Output format. One of: json|yaml")
cmd.Flags().BoolVar(&o.DryRun, "dry-run", true, "If true, only print the object that would be sent, without sending it.")

return cmd
}

func (o *EvictPodOptions) Complete(cmd *cobra.Command, args []string) error {
var err error
if len(args) != 1 {
return fmt.Errorf("pod name is required")
}
o.PodName = args[0]

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

return nil
}

func (o *EvictPodOptions) GetPrinter() (printers.ResourcePrinter, error) {
switch o.Output {
case "json":
return &printers.JSONPrinter{}, nil
case "yaml":
return &printers.YAMLPrinter{}, nil
}

return nil, fmt.Errorf("invalid output '%s'", o.Output)
}

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

return nil
}

func (o *EvictPodOptions) Run() error {
ctx := context.Background()

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

pod, err := kubeClient.GetClientset().CoreV1().Pods(o.Namespace).Get(ctx, o.PodName, metav1.GetOptions{})
if err != nil {
return err
}

dryRunOpts := []string{}
if o.DryRun {
dryRunOpts = append(dryRunOpts, "All")
}

eviction := policyv1.Eviction{
TypeMeta: metav1.TypeMeta{
Kind: "Eviction",
APIVersion: policyv1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
},
DeleteOptions: &metav1.DeleteOptions{
DryRun: dryRunOpts,
},
}

err = kubeClient.GetClientset().CoreV1().Pods(o.Namespace).EvictV1(ctx, &eviction)
if err != nil {
return err
}

w := printers.GetNewTabWriter(o.Out)
printer, err := o.GetPrinter()
if err := printer.PrintObj(&eviction, w); err != nil {
return err
}

return w.Flush()
}
12 changes: 12 additions & 0 deletions pkg/utils/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package utils

import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
)

func HandleRunError(streams genericclioptions.IOStreams, err error) error {
w := printers.GetNewTabWriter(streams.ErrOut)
w.Write([]byte(err.Error()))
return w.Flush()
}

0 comments on commit 66deeab

Please sign in to comment.