Unverified Commit 985406c9 authored by juanvallejo's avatar juanvallejo Committed by Maciej Szulik

add cli plugin example repo

parent 029bb4e2
......@@ -211,6 +211,8 @@ filegroup(
"//staging/src/k8s.io/metrics/pkg/client/custom_metrics:all-srcs",
"//staging/src/k8s.io/metrics/pkg/client/external_metrics:all-srcs",
"//staging/src/k8s.io/sample-apiserver:all-srcs",
"//staging/src/k8s.io/sample-cli-plugin/cmd:all-srcs",
"//staging/src/k8s.io/sample-cli-plugin/pkg/cmd:all-srcs",
"//staging/src/k8s.io/sample-controller:all-srcs",
],
tags = ["automanaged"],
......
# Contributing guidelines
Do not open pull requests directly against this repository, they will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes.
This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/sample-cli-plugin](https://git.k8s.io/kubernetes/staging/src/k8s.io/sample-cli-plugin) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot).
Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/staging.md) for more information
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.
approvers:
- juanvallejo
- soltysh
reviewers:
- seans3
- deads2k
- soltysh
- juanvallejo
labels:
- sig/cli
# sample-cli-plugin
This repository implements a single kubectl plugin for switching the namespace
that the current KUBECONFIG context points to. In order to remain as indestructive
as possible, no existing contexts are modified.
**Note:** go-get or vendor this package as `k8s.io/sample-cli-plugin`.
This particular example demonstrates how to perform basic operations such as:
* How to create a new custom command that follows kubectl patterns
* How to obtain a user's KUBECONFIG settings and modify them
* How to make general use of the provided "cli-runtime" set of helpers for kubectl and third-party plugins
It makes use of the genericclioptions in [k8s.io/cli-runtime](https://github.com/kubernetes/cli-runtime)
to generate a set of configuration flags which are in turn used to generate a raw representation of
the user's KUBECONFIG, as well as to obtain configuration which can be used with RESTClients when sending
requests to a kubernetes api server.
## Details
The sample cli plugin uses the [client-go library](https://github.com/kubernetes/client-go/tree/master/tools/clientcmd) to patch an existing KUBECONFIG file in a user's environment in order to update context information to point the client to a new or existing namespace.
In order to be as non-destructive as possible, no existing contexts are modified in any way. Rather, the current context is examined, and matched against existing contexts to find a context containing the same "AuthInfo" and "Cluster" information, but with the newly desired namespace requested by the user.
## Purpose
This is an example of how to build a kubectl plugin using the same set of tools and helpers available to kubectl.
## Running
```sh
# assumes you have a working KUBECONFIG
$ go build cmd/kubectl-ns.go
# place the built binary somewhere in your PATH
$ cp ./kubectl-ns /usr/local/bin
# you can now begin using this plugin as a regular kubectl command:
# update your configuration to point to "new-namespace"
$ kubectl ns new-namespace
# any kubectl commands you perform from now on will use "new-namespace"
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
new-namespace-pod 1/1 Running 0 1h
# list all of the namespace in use by contexts in your KUBECONFIG
$ kubectl ns --list
# show the namespace that the currently set context in your KUBECONFIG points to
$ kubectl ns
```
## Use Cases
This plugin can be used as a developer tool, in order to quickly view or change the current namespace
that kubectl points to.
It can also be used as a means of showcasing usage of the cli-runtime set of utilities to aid in
third-party plugin development.
## Cleanup
You can "uninstall" this plugin from kubectl by simply removing it from your PATH:
$ rm /usr/local/bin/kubectl-ns
## Compatibility
HEAD of this repository will match HEAD of k8s.io/apimachinery and
k8s.io/client-go.
## Where does it come from?
`sample-cli-plugin` is synced from
https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/sample-cli-plugin.
Code changes are made in that location, merged into k8s.io/kubernetes and
later synced here.
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
cjcullen
jessfraz
liggitt
philips
tallclair
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = ["kubectl-ns.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/sample-cli-plugin/cmd",
importpath = "k8s.io/sample-cli-plugin/cmd",
visibility = ["//visibility:private"],
deps = [
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/sample-cli-plugin/pkg/cmd:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
],
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package main
import (
"os"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/sample-cli-plugin/pkg/cmd"
)
func main() {
flags := pflag.NewFlagSet("kubectl-ns", pflag.ExitOnError)
pflag.CommandLine = flags
root := cmd.NewCmdNamespace(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
if err := root.Execute(); err != nil {
os.Exit(1)
}
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["ns.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/sample-cli-plugin/pkg/cmd",
importpath = "k8s.io/sample-cli-plugin/pkg/cmd",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var (
namespace_example = `
# view the current namespace in your KUBECONFIG
%[1]s ns
# view all of the namespaces in use by contexts in your KUBECONFIG
%[1] ns --list
# switch your current-context to one that contains the desired namespace
%[1]s ns foo
`
)
type NamespaceOptions struct {
configFlags *genericclioptions.ConfigFlags
rawConfig api.Config
listNamespaces bool
args []string
genericclioptions.IOStreams
}
func NewNamespaceOptions(streams genericclioptions.IOStreams) *NamespaceOptions {
return &NamespaceOptions{
configFlags: genericclioptions.NewConfigFlags(),
IOStreams: streams,
}
}
func NewCmdNamespace(streams genericclioptions.IOStreams) *cobra.Command {
o := NewNamespaceOptions(streams)
cmd := &cobra.Command{
Use: "ns [new-namespace] [flags]",
Short: "View or set the current namespace",
Example: namespace_example,
SilenceUsage: true,
RunE: func(c *cobra.Command, args []string) error {
if err := o.Complete(args); err != nil {
return err
}
if err := o.Validate(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
}
return nil
},
}
cmd.Flags().BoolVar(&o.listNamespaces, "list", o.listNamespaces, "if true, print the list of all namespaces in the current KUBECONFIG")
o.configFlags.AddFlags(cmd.Flags())
return cmd
}
func (o *NamespaceOptions) Complete(args []string) error {
var err error
o.rawConfig, err = o.configFlags.ToRawKubeConfigLoader().RawConfig()
if err != nil {
return err
}
o.args = args
return nil
}
func (o *NamespaceOptions) Validate() error {
if len(o.rawConfig.CurrentContext) == 0 {
return fmt.Errorf("no context is currently set in your configuration")
}
if len(o.args) > 1 {
return fmt.Errorf("either one or no arguments are allowed")
}
return nil
}
func (o *NamespaceOptions) Run() error {
if len(o.args) > 0 && len(o.args[0]) > 0 {
return o.setNamespace(o.args[0])
}
namespaces := map[string]bool{}
for name, c := range o.rawConfig.Contexts {
if !o.listNamespaces && name == o.rawConfig.CurrentContext {
if len(c.Namespace) == 0 {
return fmt.Errorf("no namespace is set for your current context: %q", name)
}
fmt.Fprintf(o.Out, "%s\n", c.Namespace)
return nil
}
// skip if dealing with a namespace we have already seen
// or if the namespace for the current context is empty
if len(c.Namespace) == 0 {
continue
}
if namespaces[c.Namespace] {
continue
}
namespaces[c.Namespace] = true
}
if !o.listNamespaces {
return fmt.Errorf("unable to find information for the current namespace in your configuration")
}
for n := range namespaces {
fmt.Fprintf(o.Out, "%s\n", n)
}
return nil
}
func (o *NamespaceOptions) setNamespace(newNamespace string) error {
if len(newNamespace) == 0 {
return fmt.Errorf("a non-empty namespace must be provided")
}
existingCtx, ok := o.rawConfig.Contexts[o.rawConfig.CurrentContext]
if !ok {
return fmt.Errorf("unable to gather information about the current context")
}
if existingCtx.Namespace == newNamespace {
fmt.Fprintf(o.Out, "already using namespace %q\n", newNamespace)
return nil
}
// determine if a context exists for the new namespace
existingCtxName := ""
for name, c := range o.rawConfig.Contexts {
if c.Namespace != newNamespace || c.Cluster != existingCtx.Cluster || c.AuthInfo != existingCtx.AuthInfo {
continue
}
existingCtxName = name
break
}
if len(existingCtxName) == 0 {
newCtx := api.NewContext()
newCtx.AuthInfo = existingCtx.AuthInfo
newCtx.Cluster = existingCtx.Cluster
newCtx.Namespace = newNamespace
newCtxName := newNamespace
if len(existingCtx.Cluster) > 0 {
newCtxName = fmt.Sprintf("%s/%s", newCtxName, existingCtx.Cluster)
}
if len(existingCtx.AuthInfo) > 0 {
cleanAuthInfo := strings.Split(existingCtx.AuthInfo, "/")[0]
newCtxName = fmt.Sprintf("%s/%s", newCtxName, cleanAuthInfo)
}
o.rawConfig.Contexts[newCtxName] = newCtx
existingCtxName = newCtxName
}
configAccess := clientcmd.NewDefaultPathOptions()
o.rawConfig.CurrentContext = existingCtxName
if err := clientcmd.ModifyConfig(configAccess, o.rawConfig, true); err != nil {
return err
}
fmt.Fprintf(o.Out, "namespace changed to %q\n", newNamespace)
return nil
}
../../staging/src/k8s.io/sample-cli-plugin
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment