Skip to content

Commit

Permalink
kubectl plugin release-0.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
AdheipSingh committed Jun 15, 2021
1 parent a8639a1 commit 3cef8db
Show file tree
Hide file tree
Showing 10 changed files with 1,269 additions and 0 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## kubectl-druid-plugin
- Kubectl plugin to simplify operations on druid CR.

## Prerequisite
- Druid CRD defination to be installed.
- A druid CR managed druid cluster running.
- https://github.com/druid-io/druid-operator/tree/master/deploy/crds

## Getting Started
- NOTE: go version 1.15
- ```go build -o kubectl-druid```
- ```mv kubectl-druid /usr/local/bin```
- ```kubectl druid --help```

## Commands

- List All Druid CR's in a k8s cluster
```
kubectl druid list
```

- List Druid CR's in a namespace
```
kubectl druid list --namespace <namespace>
```

- Get Druid Nodes's in a namespace for a specific cr
```
kubectl druid get nodes --cr <cr>--namespace <namespace>
```

- Scale Druid Replicas for a specific druid cr node in a namespace
```
kubectl druid scale --cr <cr> --namespace <namespace> --node middlemanager --replicas 4
```

- Update Image for a specific druid CR node in namespace
```
kubectl druid update --CR <CR> --image <image> --namespace <namespace> --node broker
```

- Patch Operation of CR Flags
```
kubectl druid patch --cr <cr> --namespace <namespace> --deleteOrphanPvc true
kubectl druid patch --cr <cr> --namespace <namespace> --rollingDeploy true
```
32 changes: 32 additions & 0 deletions cmd/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cmd

import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

// newClient() shall return a dynamic client
func newClient() *client {

dynamicClient, err := dynamic.NewForConfig(newConfig())
if err != nil {
panic(err.Error())
}

return &client{dynamicClient}
}

// newConfig() shall return a config
func newConfig() *rest.Config {

loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)

config, err := kubeConfig.ClientConfig()
if err != nil {
panic(err.Error())
}
return config
}
183 changes: 183 additions & 0 deletions cmd/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package cmd

import (
"context"
"encoding/json"
"sort"

v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
)

// GVK for Druid CR
var GVK = schema.GroupVersionResource{
Group: "druid.apache.org",
Version: "v1alpha1",
Resource: "druids",
}

// patchValue specifies a patch operation.
type patchValue struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}

// constructor for patchValue{}
func NewPatchValue(op, path string, value interface{}) []byte {
patchPayload := make([]patchValue, 1)

patchPayload[0].Op = op
patchPayload[0].Path = path
patchPayload[0].Value = value

bytes, _ := json.Marshal(patchPayload)
return bytes
}

// dynamicInterface holds writers,reader and patcher interfaces
type dynamicInterface interface {
writers
readers
patcher
}

// readers interface
type readers interface {
listDruidCR(namespaces string) ([]string, error)
getDruidNodeNames(namespaces, CR string) ([]string, error)
}

// writers interface
type writers interface {
writerDruidNodeSpecReplicas(nodeName, namespace, CR string, replica int64) (bool, error)
writerDruidNodeImages(nodeName, namespace, CR, image string) (bool, error)
}

// patchers interface
type patcher interface {
patcherDruidDeleteOrphanPvc(namespace, CR string, value bool) (bool, error)
patcherDruidRollingDeploy(namespace, CR string, value bool) (bool, error)
}

// client struct holds the dynamic client
type client struct {
dynamic.Interface
}

// initalize dynamicInterface
var di dynamicInterface = client{newClient()}

// getDruidNodeNames gets all the druid nodes in a namespace
func (c client) getDruidNodeNames(namespaces, CR string) ([]string, error) {

var err error

druidNodeName, err := c.Resource(GVK).Namespace(namespaces).Get(context.TODO(), CR, v1.GetOptions{})
if err != nil {
return nil, err
}

var names []string

nameLists, _, _ := unstructured.NestedMap(druidNodeName.Object, "spec", "nodes")
for nameList := range nameLists {
names = append(names, nameList)
sort.Strings(names)
}

return names, nil

}

// listDruidCR lists all the druid CR in a namespace or all namespaces
func (c client) listDruidCR(namespaces string) ([]string, error) {

var err error

druidList, err := c.Resource(GVK).Namespace(namespaces).List(context.TODO(), v1.ListOptions{})
if err != nil {
return nil, err
}

var names []string
for _, d := range druidList.Items {
names = append(names, d.GetName())
sort.Strings(names)
}

return names, nil

}

// writerNodeSpecReplicas writer nodespec replica
func (c client) writerDruidNodeSpecReplicas(nodeName, namespace, CR string, replica int64) (bool, error) {
var err error
cr, err := c.Resource(GVK).Namespace(namespace).Get(context.TODO(), CR, v1.GetOptions{})
if err != nil {
return false, err
}

if err := unstructured.SetNestedField(cr.Object, int64(replica), "spec", "nodes", nodeName, "replicas"); err != nil {
return false, err
}

_, err = c.Resource(GVK).Namespace(namespace).Update(context.TODO(), cr, v1.UpdateOptions{})
if err != nil {
return false, err
}

return true, nil
}

// writerDruidNodeImages writers updates nodes images
func (c client) writerDruidNodeImages(nodeName, namespace, CR, image string) (bool, error) {
var err error

cr, err := c.Resource(GVK).Namespace(namespace).Get(context.TODO(), CR, v1.GetOptions{})
if err != nil {
return false, err
}

if err := unstructured.SetNestedField(cr.Object, image, "spec", "nodes", nodeName, "image"); err != nil {
return false, err
}

_, err = c.Resource(GVK).Namespace(namespace).Update(context.TODO(), cr, v1.UpdateOptions{})
if err != nil {
return false, err
}

return true, nil
}

// patcherDruidDeleteOrphanPvc patches DeleteOrphanPvc flag
func (c client) patcherDruidDeleteOrphanPvc(namespace, CR string, value bool) (bool, error) {
var err error

patchBytes := NewPatchValue("replace", "/spec/deleteOrphanPvc", value)

_, err = c.Resource(GVK).Namespace(namespace).Patch(context.TODO(), CR, types.JSONPatchType, patchBytes, v1.PatchOptions{})
if err != nil {
return false, err
}

return true, nil
}

// patcherDruidRollingDeploy patches rollingDeploy flag
func (c client) patcherDruidRollingDeploy(namespace, CR string, value bool) (bool, error) {
var err error

patchBytes := NewPatchValue("replace", "/spec/rollingDeploy", value)

_, err = c.Resource(GVK).Namespace(namespace).Patch(context.TODO(), CR, types.JSONPatchType, patchBytes, v1.PatchOptions{})
if err != nil {
return false, err
}

return true, nil
}
74 changes: 74 additions & 0 deletions cmd/patchers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cmd

import (
"errors"
"fmt"
"io"
"strconv"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

type druidPatcherCmd struct {
out io.Writer
}

func druidCRPatcher(streams genericclioptions.IOStreams) *cobra.Command {
druidCmdList := &druidListCmd{
out: streams.Out,
}

var deleteOrphanPvc, rollingDeploy, namespace, cr string
cmd := &cobra.Command{
Use: "patch",
Short: "patches druid CR",
SilenceUsage: true,
RunE: func(c *cobra.Command, args []string) error {
if len(args) != 0 {
return errors.New("this command does not accept arguments")
}
return druidCmdList.druidCRPatcherRun(namespace, cr, deleteOrphanPvc, rollingDeploy)
},
}

f := cmd.Flags()
f.StringVar(&namespace, "namespace", "", "namespace of druid CR")
f.StringVar(&cr, "cr", "", "name of the druid CR")
f.StringVar(&deleteOrphanPvc, "deleteOrphanPvc", "", "deleteOrphanPvc is a bool, enabling to true will lead to delete of orphan pvc")
f.StringVar(&rollingDeploy, "rollingDeploy", "", "rollingDeploy is a bool, enabling to true will lead to sequential rolling upgrades")

return cmd
}

func (sv *druidListCmd) druidCRPatcherRun(namespace, CR, deleteOrphanPvc, rollingDeploy string) error {

if deleteOrphanPvc != "" {
b, _ := strconv.ParseBool(deleteOrphanPvc)
patcherResult, err := di.patcherDruidDeleteOrphanPvc(namespace, CR, b)
if err != nil {
return err
}

if patcherResult {
_, err := fmt.Fprintf(sv.out, "Druid CR [%s],successfully patched DeleteOrphanPvc to [%v] in Namespace [%s]\n", CR, deleteOrphanPvc, namespace)
if err != nil {
return err
}
}
} else if rollingDeploy != "" {
b, _ := strconv.ParseBool(rollingDeploy)
patcherResult, err := di.patcherDruidRollingDeploy(namespace, CR, b)
if err != nil {
return err
}

if patcherResult {
_, err := fmt.Fprintf(sv.out, "Druid CR [%s],successfully patched rollingDeploy to [%v] in Namespace [%s]\n", CR, rollingDeploy, namespace)
if err != nil {
return err
}
}
}
return nil
}
21 changes: 21 additions & 0 deletions cmd/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cmd

import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

var rootCmd = &cobra.Command{
Use: "druid",
Long: "kubectl druid plugin",
SilenceUsage: true,
}

func NewCmdDruidPlugin(streams genericclioptions.IOStreams) *cobra.Command {
rootCmd.AddCommand(druidCRList(streams))
rootCmd.AddCommand(druidCRGet(streams))
rootCmd.AddCommand(druidCRWriterNodeSpecReplicas(streams))
rootCmd.AddCommand(druidCRWriterUpdates(streams))
rootCmd.AddCommand(druidCRPatcher(streams))
return rootCmd
}
Loading

0 comments on commit 3cef8db

Please sign in to comment.