Improve kubectl plugin

This commit is contained in:
Alex Kursell 2019-03-12 12:52:23 -04:00
parent ef0b1633e2
commit 9d62ec97de
14 changed files with 1117 additions and 464 deletions

View file

@ -0,0 +1,84 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backends
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
var pod, deployment *string
cmd := &cobra.Command{
Use: "backends",
Short: "Inspect the dynamic backend information of an ingress-nginx instance",
RunE: func(cmd *cobra.Command, args []string) error {
backend, err := cmd.Flags().GetString("backend")
if err != nil {
return err
}
onlyList, err := cmd.Flags().GetBool("list")
if err != nil {
return err
}
if onlyList && backend != "" {
return fmt.Errorf("--list and --backend cannot both be specified")
}
util.PrintError(backends(flags, *pod, *deployment, backend, onlyList))
return nil
},
}
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
cmd.Flags().String("backend", "", "Output only the information for the given backend")
cmd.Flags().Bool("list", false, "Output a newline-separated list of backend names")
return cmd
}
func backends(flags *genericclioptions.ConfigFlags, podName string, deployment string, backend string, onlyList bool) error {
var command []string
if onlyList {
command = []string{"/dbg", "backends", "list"}
} else if backend != "" {
command = []string{"/dbg", "backends", "get", backend}
} else {
command = []string{"/dbg", "backends", "all"}
}
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
out, err := kubectl.PodExecString(flags, &pod, command)
if err != nil {
return err
}
fmt.Printf(out)
return nil
}

View file

@ -0,0 +1,70 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certs
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
var pod, deployment *string
cmd := &cobra.Command{
Use: "certs",
Short: "Output the certificate data stored in an ingress-nginx pod",
RunE: func(cmd *cobra.Command, args []string) error {
host, err := cmd.Flags().GetString("host")
if err != nil {
return err
}
util.PrintError(certs(flags, *pod, *deployment, host))
return nil
},
}
cmd.Flags().String("host", "", "Get the cert for this hostname")
cobra.MarkFlagRequired(cmd.Flags(), "host")
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
return cmd
}
func certs(flags *genericclioptions.ConfigFlags, podName string, deployment string, host string) error {
command := []string{"/dbg", "certs", "get", host}
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
out, err := kubectl.PodExecString(flags, &pod, command)
if err != nil {
return err
}
fmt.Print(out)
return nil
}

View file

@ -0,0 +1,78 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conf
import (
"fmt"
"github.com/spf13/cobra"
"strings"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
"k8s.io/ingress-nginx/internal/nginx"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
var pod, deployment *string
cmd := &cobra.Command{
Use: "conf",
Short: "Inspect the generated nginx.conf",
RunE: func(cmd *cobra.Command, args []string) error {
host, err := cmd.Flags().GetString("host")
if err != nil {
return err
}
util.PrintError(conf(flags, host, *pod, *deployment))
return nil
},
}
cmd.Flags().String("host", "", "Print just the server block with this hostname")
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
return cmd
}
func conf(flags *genericclioptions.ConfigFlags, host string, podName string, deployment string) error {
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
nginxConf, err := kubectl.PodExecString(flags, &pod, []string{"/dbg", "conf"})
if err != nil {
return err
}
if host != "" {
block, err := nginx.GetServerBlock(nginxConf, host)
if err != nil {
return err
}
fmt.Println(strings.TrimRight(strings.Trim(block, " \n"), " \n\t"))
} else {
fmt.Print(nginxConf)
}
return nil
}

View file

@ -0,0 +1,72 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package exec
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
opts := execFlags{}
var pod, deployment *string
cmd := &cobra.Command{
Use: "exec",
Short: "Execute a command inside an ingress-nginx pod",
RunE: func(cmd *cobra.Command, args []string) error {
util.PrintError(exec(flags, *pod, *deployment, args, opts))
return nil
},
}
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
cmd.Flags().BoolVarP(&opts.TTY, "tty", "t", false, "Stdin is a TTY")
cmd.Flags().BoolVarP(&opts.Stdin, "stdin", "i", false, "Pass stdin to the container")
return cmd
}
type execFlags struct {
TTY bool
Stdin bool
}
func exec(flags *genericclioptions.ConfigFlags, podName string, deployment string, cmd []string, opts execFlags) error {
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
args := []string{"exec"}
if opts.TTY {
args = append(args, "-t")
}
if opts.Stdin {
args = append(args, "-i")
}
args = append(args, []string{"-n", pod.Namespace, pod.Name, "--"}...)
args = append(args, cmd...)
return kubectl.Exec(flags, args)
}

View file

@ -0,0 +1,60 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package general
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
var pod, deployment *string
cmd := &cobra.Command{
Use: "general",
Short: "Inspect the other dynamic ingress-nginx information",
RunE: func(cmd *cobra.Command, args []string) error {
util.PrintError(general(flags, *pod, *deployment))
return nil
},
}
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
return cmd
}
func general(flags *genericclioptions.ConfigFlags, podName string, deployment string) error {
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
out, err := kubectl.PodExecString(flags, &pod, []string{"/dbg", "general"})
if err != nil {
return err
}
fmt.Print(out)
return nil
}

View file

@ -0,0 +1,58 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package info
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
cmd := &cobra.Command{
Use: "info",
Short: "Show information about the ingress-nginx service",
RunE: func(cmd *cobra.Command, args []string) error {
service, err := cmd.Flags().GetString("service")
if err != nil {
return err
}
util.PrintError(info(flags, service))
return nil
},
}
cmd.Flags().String("service", util.DefaultIngressServiceName, "The name of the ingress-nginx service")
return cmd
}
func info(flags *genericclioptions.ConfigFlags, serviceName string) error {
service, err := request.GetServiceByName(flags, serviceName, nil)
if err != nil {
return err
}
fmt.Printf("Service cluster IP address: %v\n", service.Spec.ClusterIP)
fmt.Printf("LoadBalancer IP|CNAME: %v\n", service.Spec.LoadBalancerIP)
return nil
}

View file

@ -0,0 +1,217 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ingresses
import (
"fmt"
"github.com/spf13/cobra"
"os"
"text/tabwriter"
"k8s.io/api/extensions/v1beta1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
cmd := &cobra.Command{
Use: "ingresses",
Aliases: []string{"ingress", "ing"},
Short: "Provide a short summary of all of the ingress definitions",
RunE: func(cmd *cobra.Command, args []string) error {
host, err := cmd.Flags().GetString("host")
if err != nil {
return err
}
allNamespaces, err := cmd.Flags().GetBool("all-namespaces")
if err != nil {
return err
}
util.PrintError(ingresses(flags, host, allNamespaces))
return nil
},
}
cmd.Flags().String("host", "", "Show just the ingress definitions for this hostname")
cmd.Flags().Bool("all-namespaces", false, "Find ingress definitions from all namespaces")
return cmd
}
func ingresses(flags *genericclioptions.ConfigFlags, host string, allNamespaces bool) error {
var namespace string
if allNamespaces {
namespace = ""
} else {
namespace = util.GetNamespace(flags)
}
ingresses, err := request.GetIngressDefinitions(flags, namespace)
if err != nil {
return err
}
rows := getIngressRows(&ingresses)
if host != "" {
rowsWithHost := make([]ingressRow, 0)
for _, row := range rows {
if row.Host == host {
rowsWithHost = append(rowsWithHost, row)
}
}
rows = rowsWithHost
}
printer := tabwriter.NewWriter(os.Stdout, 6, 4, 3, ' ', 0)
defer printer.Flush()
if allNamespaces {
fmt.Fprintln(printer, "NAMESPACE\tINGRESS NAME\tHOST+PATH\tADDRESSES\tTLS\tSERVICE\tSERVICE PORT\tENDPOINTS")
} else {
fmt.Fprintln(printer, "INGRESS NAME\tHOST+PATH\tADDRESSES\tTLS\tSERVICE\tSERVICE PORT\tENDPOINTS")
}
for _, row := range rows {
var tlsMsg string
if row.TLS {
tlsMsg = "YES"
} else {
tlsMsg = "NO"
}
numEndpoints, err := request.GetNumEndpoints(flags, row.Namespace, row.ServiceName)
if err != nil {
return err
}
if numEndpoints == nil {
row.NumEndpoints = "N/A"
} else {
row.NumEndpoints = fmt.Sprint(*numEndpoints)
}
if allNamespaces {
fmt.Fprintf(printer, "%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", row.Namespace, row.IngressName, row.Host+row.Path, row.Address, tlsMsg, row.ServiceName, row.ServicePort, row.NumEndpoints)
} else {
fmt.Fprintf(printer, "%v\t%v\t%v\t%v\t%v\t%v\t%v\n", row.IngressName, row.Host+row.Path, row.Address, tlsMsg, row.ServiceName, row.ServicePort, row.NumEndpoints)
}
}
return nil
}
type ingressRow struct {
Namespace string
IngressName string
Host string
Path string
TLS bool
ServiceName string
ServicePort string
Address string
NumEndpoints string
}
func getIngressRows(ingresses *[]v1beta1.Ingress) []ingressRow {
rows := make([]ingressRow, 0)
for _, ing := range *ingresses {
address := ""
for _, lbIng := range ing.Status.LoadBalancer.Ingress {
if len(lbIng.IP) > 0 {
address = address + lbIng.IP + ","
}
if len(lbIng.Hostname) > 0 {
address = address + lbIng.Hostname + ","
}
}
if len(address) > 0 {
address = address[:len(address)-1]
}
tlsHosts := make(map[string]struct{})
for _, tls := range ing.Spec.TLS {
for _, host := range tls.Hosts {
tlsHosts[host] = struct{}{}
}
}
defaultBackendService := ""
defaultBackendPort := ""
if ing.Spec.Backend != nil {
defaultBackendService = ing.Spec.Backend.ServiceName
defaultBackendPort = ing.Spec.Backend.ServicePort.String()
}
// Handle catch-all ingress
if len(ing.Spec.Rules) == 0 && len(defaultBackendService) > 0 {
row := ingressRow{
Namespace: ing.Namespace,
IngressName: ing.Name,
Host: "*",
ServiceName: defaultBackendService,
ServicePort: defaultBackendPort,
Address: address,
}
rows = append(rows, row)
continue
}
for _, rule := range ing.Spec.Rules {
_, hasTLS := tlsHosts[rule.Host]
//Handle ingress with no paths
if rule.HTTP == nil {
row := ingressRow{
Namespace: ing.Namespace,
IngressName: ing.Name,
Host: rule.Host,
Path: "",
TLS: hasTLS,
ServiceName: defaultBackendService,
ServicePort: defaultBackendPort,
Address: address,
}
rows = append(rows, row)
continue
}
for _, path := range rule.HTTP.Paths {
row := ingressRow{
Namespace: ing.Namespace,
IngressName: ing.Name,
Host: rule.Host,
Path: path.Path,
TLS: hasTLS,
ServiceName: path.Backend.ServiceName,
ServicePort: path.Backend.ServicePort.String(),
Address: address,
}
rows = append(rows, row)
}
}
}
return rows
}

View file

@ -0,0 +1,108 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package logs
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
o := logsFlags{}
var pod, deployment *string
cmd := &cobra.Command{
Use: "logs",
Short: "Get the kubernetes logs for an ingress-nginx pod",
RunE: func(cmd *cobra.Command, args []string) error {
util.PrintError(logs(flags, *pod, *deployment, o))
return nil
},
}
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.")
cmd.Flags().BoolVar(&o.Timestamps, "timestamps", o.Timestamps, "Include timestamps on each line in the log output")
cmd.Flags().Int64Var(&o.LimitBytes, "limit-bytes", o.LimitBytes, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().BoolVarP(&o.Previous, "previous", "p", o.Previous, "If true, print the logs for the previous instance of the container in a pod if it exists.")
cmd.Flags().Int64Var(&o.Tail, "tail", o.Tail, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
cmd.Flags().StringVar(&o.SinceTime, "since-time", o.SinceTime, "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringVar(&o.SinceSeconds, "since", o.SinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on.")
return cmd
}
type logsFlags struct {
SinceTime string
SinceSeconds string
Follow bool
Previous bool
Timestamps bool
LimitBytes int64
Tail int64
Selector string
}
func (o *logsFlags) toStrings() []string {
r := []string{}
if o.SinceTime != "" {
r = append(r, "--since-time", o.SinceTime)
}
if o.SinceSeconds != "" {
r = append(r, "--since", o.SinceSeconds)
}
if o.Follow {
r = append(r, "--follow")
}
if o.Previous {
r = append(r, "--previous")
}
if o.Timestamps {
r = append(r, "--timestamps")
}
if o.LimitBytes != 0 {
r = append(r, "--limit-bytes", fmt.Sprintf("%v", o.LimitBytes))
}
if o.Tail != 0 {
r = append(r, "--tail", fmt.Sprintf("%v", o.Tail))
}
if o.Selector != "" {
r = append(r, "--selector", o.Selector)
}
return r
}
func logs(flags *genericclioptions.ConfigFlags, podName string, deployment string, opts logsFlags) error {
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
cmd := []string{"logs", "-n", pod.Namespace, pod.Name}
cmd = append(cmd, opts.toStrings()...)
return kubectl.Exec(flags, cmd)
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ssh
import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/ingress-nginx/cmd/plugin/kubectl"
"k8s.io/ingress-nginx/cmd/plugin/request"
"k8s.io/ingress-nginx/cmd/plugin/util"
)
// CreateCommand creates and returns this cobra subcommand
func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
var pod, deployment *string
cmd := &cobra.Command{
Use: "ssh",
Short: "ssh into a running ingress-nginx pod",
RunE: func(cmd *cobra.Command, args []string) error {
util.PrintError(ssh(flags, *pod, *deployment))
return nil
},
}
pod = util.AddPodFlag(cmd)
deployment = util.AddDeploymentFlag(cmd)
return cmd
}
func ssh(flags *genericclioptions.ConfigFlags, podName string, deployment string) error {
pod, err := request.ChoosePod(flags, podName, deployment)
if err != nil {
return err
}
return kubectl.Exec(flags, []string{"exec", "-it", "-n", pod.Namespace, pod.Name, "--", "/bin/bash"})
}