devlopment branch (#1736)
This commit is contained in:
260
vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
generated
vendored
Normal file
260
vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
Copyright 2014 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 get
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/liggitt/tabwriter"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
)
|
||||
|
||||
var jsonRegexp = regexp.MustCompile(`^\{\.?([^{}]+)\}$|^\.?([^{}]+)$`)
|
||||
|
||||
// RelaxedJSONPathExpression attempts to be flexible with JSONPath expressions, it accepts:
|
||||
// * metadata.name (no leading '.' or curly braces '{...}'
|
||||
// * {metadata.name} (no leading '.')
|
||||
// * .metadata.name (no curly braces '{...}')
|
||||
// * {.metadata.name} (complete expression)
|
||||
// And transforms them all into a valid jsonpath expression:
|
||||
// {.metadata.name}
|
||||
func RelaxedJSONPathExpression(pathExpression string) (string, error) {
|
||||
if len(pathExpression) == 0 {
|
||||
return pathExpression, nil
|
||||
}
|
||||
submatches := jsonRegexp.FindStringSubmatch(pathExpression)
|
||||
if submatches == nil {
|
||||
return "", fmt.Errorf("unexpected path string, expected a 'name1.name2' or '.name1.name2' or '{name1.name2}' or '{.name1.name2}'")
|
||||
}
|
||||
if len(submatches) != 3 {
|
||||
return "", fmt.Errorf("unexpected submatch list: %v", submatches)
|
||||
}
|
||||
var fieldSpec string
|
||||
if len(submatches[1]) != 0 {
|
||||
fieldSpec = submatches[1]
|
||||
} else {
|
||||
fieldSpec = submatches[2]
|
||||
}
|
||||
return fmt.Sprintf("{.%s}", fieldSpec), nil
|
||||
}
|
||||
|
||||
// NewCustomColumnsPrinterFromSpec creates a custom columns printer from a comma separated list of <header>:<jsonpath-field-spec> pairs.
|
||||
// e.g. NAME:metadata.name,API_VERSION:apiVersion creates a printer that prints:
|
||||
//
|
||||
// NAME API_VERSION
|
||||
// foo bar
|
||||
func NewCustomColumnsPrinterFromSpec(spec string, decoder runtime.Decoder, noHeaders bool) (*CustomColumnsPrinter, error) {
|
||||
if len(spec) == 0 {
|
||||
return nil, fmt.Errorf("custom-columns format specified but no custom columns given")
|
||||
}
|
||||
parts := strings.Split(spec, ",")
|
||||
columns := make([]Column, len(parts))
|
||||
for ix := range parts {
|
||||
colSpec := strings.SplitN(parts[ix], ":", 2)
|
||||
if len(colSpec) != 2 {
|
||||
return nil, fmt.Errorf("unexpected custom-columns spec: %s, expected <header>:<json-path-expr>", parts[ix])
|
||||
}
|
||||
spec, err := RelaxedJSONPathExpression(colSpec[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns[ix] = Column{Header: colSpec[0], FieldSpec: spec}
|
||||
}
|
||||
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: noHeaders}, nil
|
||||
}
|
||||
|
||||
func splitOnWhitespace(line string) []string {
|
||||
lineScanner := bufio.NewScanner(bytes.NewBufferString(line))
|
||||
lineScanner.Split(bufio.ScanWords)
|
||||
result := []string{}
|
||||
for lineScanner.Scan() {
|
||||
result = append(result, lineScanner.Text())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// NewCustomColumnsPrinterFromTemplate creates a custom columns printer from a template stream. The template is expected
|
||||
// to consist of two lines, whitespace separated. The first line is the header line, the second line is the jsonpath field spec
|
||||
// For example, the template below:
|
||||
// NAME API_VERSION
|
||||
// {metadata.name} {apiVersion}
|
||||
func NewCustomColumnsPrinterFromTemplate(templateReader io.Reader, decoder runtime.Decoder) (*CustomColumnsPrinter, error) {
|
||||
scanner := bufio.NewScanner(templateReader)
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("invalid template, missing header line. Expected format is one line of space separated headers, one line of space separated column specs.")
|
||||
}
|
||||
headers := splitOnWhitespace(scanner.Text())
|
||||
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("invalid template, missing spec line. Expected format is one line of space separated headers, one line of space separated column specs.")
|
||||
}
|
||||
specs := splitOnWhitespace(scanner.Text())
|
||||
|
||||
if len(headers) != len(specs) {
|
||||
return nil, fmt.Errorf("number of headers (%d) and field specifications (%d) don't match", len(headers), len(specs))
|
||||
}
|
||||
|
||||
columns := make([]Column, len(headers))
|
||||
for ix := range headers {
|
||||
spec, err := RelaxedJSONPathExpression(specs[ix])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
columns[ix] = Column{
|
||||
Header: headers[ix],
|
||||
FieldSpec: spec,
|
||||
}
|
||||
}
|
||||
return &CustomColumnsPrinter{Columns: columns, Decoder: decoder, NoHeaders: false}, nil
|
||||
}
|
||||
|
||||
// Column represents a user specified column
|
||||
type Column struct {
|
||||
// The header to print above the column, general style is ALL_CAPS
|
||||
Header string
|
||||
// The pointer to the field in the object to print in JSONPath form
|
||||
// e.g. {.ObjectMeta.Name}, see pkg/util/jsonpath for more details.
|
||||
FieldSpec string
|
||||
}
|
||||
|
||||
// CustomColumnPrinter is a printer that knows how to print arbitrary columns
|
||||
// of data from templates specified in the `Columns` array
|
||||
type CustomColumnsPrinter struct {
|
||||
Columns []Column
|
||||
Decoder runtime.Decoder
|
||||
NoHeaders bool
|
||||
// lastType records type of resource printed last so that we don't repeat
|
||||
// header while printing same type of resources.
|
||||
lastType reflect.Type
|
||||
}
|
||||
|
||||
func (s *CustomColumnsPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
|
||||
// we use reflect.Indirect here in order to obtain the actual value from a pointer.
|
||||
// we need an actual value in order to retrieve the package path for an object.
|
||||
// using reflect.Indirect indiscriminately is valid here, as all runtime.Objects are supposed to be pointers.
|
||||
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(obj)).Type().PkgPath()) {
|
||||
return fmt.Errorf(printers.InternalObjectPrinterErr)
|
||||
}
|
||||
|
||||
if w, found := out.(*tabwriter.Writer); !found {
|
||||
w = printers.GetNewTabWriter(out)
|
||||
out = w
|
||||
defer w.Flush()
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(obj)
|
||||
if !s.NoHeaders && t != s.lastType {
|
||||
headers := make([]string, len(s.Columns))
|
||||
for ix := range s.Columns {
|
||||
headers[ix] = s.Columns[ix].Header
|
||||
}
|
||||
fmt.Fprintln(out, strings.Join(headers, "\t"))
|
||||
s.lastType = t
|
||||
}
|
||||
parsers := make([]*jsonpath.JSONPath, len(s.Columns))
|
||||
for ix := range s.Columns {
|
||||
parsers[ix] = jsonpath.New(fmt.Sprintf("column%d", ix)).AllowMissingKeys(true)
|
||||
if err := parsers[ix].Parse(s.Columns[ix].FieldSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if meta.IsListType(obj) {
|
||||
objs, err := meta.ExtractList(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ix := range objs {
|
||||
if err := s.printOneObject(objs[ix], parsers, out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := s.printOneObject(obj, parsers, out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jsonpath.JSONPath, out io.Writer) error {
|
||||
columns := make([]string, len(parsers))
|
||||
switch u := obj.(type) {
|
||||
case *metav1.WatchEvent:
|
||||
if printers.InternalObjectPreventer.IsForbidden(reflect.Indirect(reflect.ValueOf(u.Object.Object)).Type().PkgPath()) {
|
||||
return fmt.Errorf(printers.InternalObjectPrinterErr)
|
||||
}
|
||||
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(u.Object.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj = &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"type": u.Type,
|
||||
"object": unstructuredObject,
|
||||
},
|
||||
}
|
||||
|
||||
case *runtime.Unknown:
|
||||
if len(u.Raw) > 0 {
|
||||
var err error
|
||||
if obj, err = runtime.Decode(s.Decoder, u.Raw); err != nil {
|
||||
return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.Raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ix := range parsers {
|
||||
parser := parsers[ix]
|
||||
|
||||
var values [][]reflect.Value
|
||||
var err error
|
||||
if unstructured, ok := obj.(runtime.Unstructured); ok {
|
||||
values, err = parser.FindResults(unstructured.UnstructuredContent())
|
||||
} else {
|
||||
values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueStrings := []string{}
|
||||
if len(values) == 0 || len(values[0]) == 0 {
|
||||
valueStrings = append(valueStrings, "<none>")
|
||||
}
|
||||
for arrIx := range values {
|
||||
for valIx := range values[arrIx] {
|
||||
valueStrings = append(valueStrings, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
|
||||
}
|
||||
}
|
||||
columns[ix] = strings.Join(valueStrings, ",")
|
||||
}
|
||||
fmt.Fprintln(out, strings.Join(columns, "\t"))
|
||||
return nil
|
||||
}
|
||||
111
vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn_flags.go
generated
vendored
Normal file
111
vendor/k8s.io/kubectl/pkg/cmd/get/customcolumn_flags.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2018 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 get
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
var columnsFormats = map[string]bool{
|
||||
"custom-columns-file": true,
|
||||
"custom-columns": true,
|
||||
}
|
||||
|
||||
// CustomColumnsPrintFlags provides default flags necessary for printing
|
||||
// custom resource columns from an inline-template or file.
|
||||
type CustomColumnsPrintFlags struct {
|
||||
NoHeaders bool
|
||||
TemplateArgument string
|
||||
}
|
||||
|
||||
func (f *CustomColumnsPrintFlags) AllowedFormats() []string {
|
||||
formats := make([]string, 0, len(columnsFormats))
|
||||
for format := range columnsFormats {
|
||||
formats = append(formats, format)
|
||||
}
|
||||
return formats
|
||||
}
|
||||
|
||||
// ToPrinter receives an templateFormat and returns a printer capable of
|
||||
// handling custom-column printing.
|
||||
// Returns false if the specified templateFormat does not match a supported format.
|
||||
// Supported format types can be found in pkg/printers/printers.go
|
||||
func (f *CustomColumnsPrintFlags) ToPrinter(templateFormat string) (printers.ResourcePrinter, error) {
|
||||
if len(templateFormat) == 0 {
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{}
|
||||
}
|
||||
|
||||
templateValue := ""
|
||||
|
||||
if len(f.TemplateArgument) == 0 {
|
||||
for format := range columnsFormats {
|
||||
format = format + "="
|
||||
if strings.HasPrefix(templateFormat, format) {
|
||||
templateValue = templateFormat[len(format):]
|
||||
templateFormat = format[:len(format)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
templateValue = f.TemplateArgument
|
||||
}
|
||||
|
||||
if _, supportedFormat := columnsFormats[templateFormat]; !supportedFormat {
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &templateFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
if len(templateValue) == 0 {
|
||||
return nil, fmt.Errorf("custom-columns format specified but no custom columns given")
|
||||
}
|
||||
|
||||
// UniversalDecoder call must specify parameter versions; otherwise it will decode to internal versions.
|
||||
decoder := scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
|
||||
if templateFormat == "custom-columns-file" {
|
||||
file, err := os.Open(templateValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading template %s, %v\n", templateValue, err)
|
||||
}
|
||||
defer file.Close()
|
||||
p, err := NewCustomColumnsPrinterFromTemplate(file, decoder)
|
||||
return p, err
|
||||
}
|
||||
|
||||
return NewCustomColumnsPrinterFromSpec(templateValue, decoder, f.NoHeaders)
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to custom-columns printing
|
||||
func (f *CustomColumnsPrintFlags) AddFlags(c *cobra.Command) {}
|
||||
|
||||
// NewCustomColumnsPrintFlags returns flags associated with
|
||||
// custom-column printing, with default values set.
|
||||
// NoHeaders and TemplateArgument should be set by callers.
|
||||
func NewCustomColumnsPrintFlags() *CustomColumnsPrintFlags {
|
||||
return &CustomColumnsPrintFlags{
|
||||
NoHeaders: false,
|
||||
TemplateArgument: "",
|
||||
}
|
||||
}
|
||||
853
vendor/k8s.io/kubectl/pkg/cmd/get/get.go
generated
vendored
Normal file
853
vendor/k8s.io/kubectl/pkg/cmd/get/get.go
generated
vendored
Normal file
@@ -0,0 +1,853 @@
|
||||
/*
|
||||
Copyright 2014 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 get
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kapierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
kubernetesscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/rawhttp"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/interrupt"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
// GetOptions contains the input to the get command.
|
||||
type GetOptions struct {
|
||||
PrintFlags *PrintFlags
|
||||
ToPrinter func(*meta.RESTMapping, *bool, bool, bool) (printers.ResourcePrinterFunc, error)
|
||||
IsHumanReadablePrinter bool
|
||||
PrintWithOpenAPICols bool
|
||||
|
||||
CmdParent string
|
||||
|
||||
resource.FilenameOptions
|
||||
|
||||
Raw string
|
||||
Watch bool
|
||||
WatchOnly bool
|
||||
ChunkSize int64
|
||||
|
||||
OutputWatchEvents bool
|
||||
|
||||
LabelSelector string
|
||||
FieldSelector string
|
||||
AllNamespaces bool
|
||||
Namespace string
|
||||
ExplicitNamespace bool
|
||||
|
||||
ServerPrint bool
|
||||
|
||||
NoHeaders bool
|
||||
Sort bool
|
||||
IgnoreNotFound bool
|
||||
Export bool
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
var (
|
||||
getLong = templates.LongDesc(`
|
||||
Display one or many resources
|
||||
|
||||
Prints a table of the most important information about the specified resources.
|
||||
You can filter the list using a label selector and the --selector flag. If the
|
||||
desired resource type is namespaced you will only see results in your current
|
||||
namespace unless you pass --all-namespaces.
|
||||
|
||||
Uninitialized objects are not shown unless --include-uninitialized is passed.
|
||||
|
||||
By specifying the output as 'template' and providing a Go template as the value
|
||||
of the --template flag, you can filter the attributes of the fetched resources.`)
|
||||
|
||||
getExample = templates.Examples(i18n.T(`
|
||||
# List all pods in ps output format.
|
||||
kubectl get pods
|
||||
|
||||
# List all pods in ps output format with more information (such as node name).
|
||||
kubectl get pods -o wide
|
||||
|
||||
# List a single replication controller with specified NAME in ps output format.
|
||||
kubectl get replicationcontroller web
|
||||
|
||||
# List deployments in JSON output format, in the "v1" version of the "apps" API group:
|
||||
kubectl get deployments.v1.apps -o json
|
||||
|
||||
# List a single pod in JSON output format.
|
||||
kubectl get -o json pod web-pod-13je7
|
||||
|
||||
# List a pod identified by type and name specified in "pod.yaml" in JSON output format.
|
||||
kubectl get -f pod.yaml -o json
|
||||
|
||||
# List resources from a directory with kustomization.yaml - e.g. dir/kustomization.yaml.
|
||||
kubectl get -k dir/
|
||||
|
||||
# Return only the phase value of the specified pod.
|
||||
kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}}
|
||||
|
||||
# List resource information in custom columns.
|
||||
kubectl get pod test-pod -o custom-columns=CONTAINER:.spec.containers[0].name,IMAGE:.spec.containers[0].image
|
||||
|
||||
# List all replication controllers and services together in ps output format.
|
||||
kubectl get rc,services
|
||||
|
||||
# List one or more resources by their type and names.
|
||||
kubectl get rc/web service/frontend pods/web-pod-13je7`))
|
||||
)
|
||||
|
||||
const (
|
||||
useOpenAPIPrintColumnFlagLabel = "use-openapi-print-columns"
|
||||
useServerPrintColumns = "server-print"
|
||||
)
|
||||
|
||||
// NewGetOptions returns a GetOptions with default chunk size 500.
|
||||
func NewGetOptions(parent string, streams genericclioptions.IOStreams) *GetOptions {
|
||||
return &GetOptions{
|
||||
PrintFlags: NewGetPrintFlags(),
|
||||
CmdParent: parent,
|
||||
|
||||
IOStreams: streams,
|
||||
ChunkSize: 500,
|
||||
ServerPrint: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdGet creates a command object for the generic "get" action, which
|
||||
// retrieves one or more resources from a server.
|
||||
func NewCmdGet(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewGetOptions(parent, streams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE[.VERSION][.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Display one or many resources"),
|
||||
Long: getLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
|
||||
Example: getExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(o.Validate(cmd))
|
||||
cmdutil.CheckErr(o.Run(f, cmd, args))
|
||||
},
|
||||
SuggestFor: []string{"list", "ps"},
|
||||
}
|
||||
|
||||
o.PrintFlags.AddFlags(cmd)
|
||||
|
||||
cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to request from the server. Uses the transport specified by the kubeconfig file.")
|
||||
cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes. Uninitialized objects are excluded if no object name is provided.")
|
||||
cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.")
|
||||
cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.")
|
||||
cmd.Flags().Int64Var(&o.ChunkSize, "chunk-size", o.ChunkSize, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.")
|
||||
cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
|
||||
cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
||||
cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
|
||||
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
|
||||
addOpenAPIPrintColumnFlags(cmd, o)
|
||||
addServerPrintColumnFlags(cmd, o)
|
||||
cmd.Flags().BoolVar(&o.Export, "export", o.Export, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.")
|
||||
cmd.Flags().MarkDeprecated("export", "This flag is deprecated and will be removed in future.")
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Complete takes the command arguments and factory and infers any remaining options.
|
||||
func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
if len(o.Raw) > 0 {
|
||||
if len(args) > 0 {
|
||||
return fmt.Errorf("arguments may not be passed when --raw is specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
o.Namespace, o.ExplicitNamespace, err = f.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.AllNamespaces {
|
||||
o.ExplicitNamespace = false
|
||||
}
|
||||
|
||||
sortBy, err := cmd.Flags().GetString("sort-by")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Sort = len(sortBy) > 0
|
||||
|
||||
o.NoHeaders = cmdutil.GetFlagBool(cmd, "no-headers")
|
||||
|
||||
// TODO (soltysh): currently we don't support custom columns
|
||||
// with server side print. So in these cases force the old behavior.
|
||||
outputOption := cmd.Flags().Lookup("output").Value.String()
|
||||
if outputOption == "custom-columns" {
|
||||
o.ServerPrint = false
|
||||
}
|
||||
|
||||
templateArg := ""
|
||||
if o.PrintFlags.TemplateFlags != nil && o.PrintFlags.TemplateFlags.TemplateArgument != nil {
|
||||
templateArg = *o.PrintFlags.TemplateFlags.TemplateArgument
|
||||
}
|
||||
|
||||
// human readable printers have special conversion rules, so we determine if we're using one.
|
||||
if (len(*o.PrintFlags.OutputFormat) == 0 && len(templateArg) == 0) || *o.PrintFlags.OutputFormat == "wide" {
|
||||
o.IsHumanReadablePrinter = true
|
||||
}
|
||||
|
||||
o.ToPrinter = func(mapping *meta.RESTMapping, outputObjects *bool, withNamespace bool, withKind bool) (printers.ResourcePrinterFunc, error) {
|
||||
// make a new copy of current flags / opts before mutating
|
||||
printFlags := o.PrintFlags.Copy()
|
||||
|
||||
if mapping != nil {
|
||||
if !cmdSpecifiesOutputFmt(cmd) && o.PrintWithOpenAPICols {
|
||||
if apiSchema, err := f.OpenAPISchema(); err == nil {
|
||||
printFlags.UseOpenAPIColumns(apiSchema, mapping)
|
||||
}
|
||||
}
|
||||
printFlags.SetKind(mapping.GroupVersionKind.GroupKind())
|
||||
}
|
||||
if withNamespace {
|
||||
printFlags.EnsureWithNamespace()
|
||||
}
|
||||
if withKind {
|
||||
printFlags.EnsureWithKind()
|
||||
}
|
||||
|
||||
printer, err := printFlags.ToPrinter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
printer, err = printers.NewTypeSetter(scheme.Scheme).WrapToPrinter(printer, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.Sort {
|
||||
printer = &SortingPrinter{Delegate: printer, SortField: sortBy}
|
||||
}
|
||||
if outputObjects != nil {
|
||||
printer = &skipPrinter{delegate: printer, output: outputObjects}
|
||||
}
|
||||
if o.ServerPrint {
|
||||
printer = &TablePrinter{Delegate: printer}
|
||||
}
|
||||
return printer.PrintObj, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case o.Watch || o.WatchOnly:
|
||||
if o.Sort {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --watch or --watch-only requested, --sort-by will be ignored\n")
|
||||
}
|
||||
default:
|
||||
if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
|
||||
fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent))
|
||||
fullCmdName := cmd.Parent().CommandPath()
|
||||
usageString := "Required resource not specified."
|
||||
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "explain") {
|
||||
usageString = fmt.Sprintf("%s\nUse \"%s explain <resource>\" for a detailed description of that resource (e.g. %[2]s explain pods).", usageString, fullCmdName)
|
||||
}
|
||||
|
||||
return cmdutil.UsageErrorf(cmd, usageString)
|
||||
}
|
||||
}
|
||||
|
||||
// openapi printing is mutually exclusive with server side printing
|
||||
if o.PrintWithOpenAPICols && o.ServerPrint {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "warning: --%s requested, --%s will be ignored\n", useOpenAPIPrintColumnFlagLabel, useServerPrintColumns)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks the set of flags provided by the user.
|
||||
func (o *GetOptions) Validate(cmd *cobra.Command) error {
|
||||
if len(o.Raw) > 0 {
|
||||
if o.Watch || o.WatchOnly || len(o.LabelSelector) > 0 || o.Export {
|
||||
return fmt.Errorf("--raw may not be specified with other flags that filter the server request or alter the output")
|
||||
}
|
||||
if len(cmdutil.GetFlagString(cmd, "output")) > 0 {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw and --output are mutually exclusive")
|
||||
}
|
||||
if _, err := url.ParseRequestURI(o.Raw); err != nil {
|
||||
return cmdutil.UsageErrorf(cmd, "--raw must be a valid URL path: %v", err)
|
||||
}
|
||||
}
|
||||
if cmdutil.GetFlagBool(cmd, "show-labels") {
|
||||
outputOption := cmd.Flags().Lookup("output").Value.String()
|
||||
if outputOption != "" && outputOption != "wide" {
|
||||
return fmt.Errorf("--show-labels option cannot be used with %s printer", outputOption)
|
||||
}
|
||||
}
|
||||
if o.OutputWatchEvents && !(o.Watch || o.WatchOnly) {
|
||||
return cmdutil.UsageErrorf(cmd, "--output-watch-events option can only be used with --watch or --watch-only")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OriginalPositioner and NopPositioner is required for swap/sort operations of data in table format
|
||||
type OriginalPositioner interface {
|
||||
OriginalPosition(int) int
|
||||
}
|
||||
|
||||
// NopPositioner and OriginalPositioner is required for swap/sort operations of data in table format
|
||||
type NopPositioner struct{}
|
||||
|
||||
// OriginalPosition returns the original position from NopPositioner object
|
||||
func (t *NopPositioner) OriginalPosition(ix int) int {
|
||||
return ix
|
||||
}
|
||||
|
||||
// RuntimeSorter holds the required objects to perform sorting of runtime objects
|
||||
type RuntimeSorter struct {
|
||||
field string
|
||||
decoder runtime.Decoder
|
||||
objects []runtime.Object
|
||||
positioner OriginalPositioner
|
||||
}
|
||||
|
||||
// Sort performs the sorting of runtime objects
|
||||
func (r *RuntimeSorter) Sort() error {
|
||||
// a list is only considered "sorted" if there are 0 or 1 items in it
|
||||
// AND (if 1 item) the item is not a Table object
|
||||
if len(r.objects) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(r.objects) == 1 {
|
||||
_, isTable := r.objects[0].(*metav1beta1.Table)
|
||||
if !isTable {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
includesTable := false
|
||||
includesRuntimeObjs := false
|
||||
|
||||
for _, obj := range r.objects {
|
||||
switch t := obj.(type) {
|
||||
case *metav1beta1.Table:
|
||||
includesTable = true
|
||||
|
||||
if sorter, err := NewTableSorter(t, r.field); err != nil {
|
||||
return err
|
||||
} else if err := sorter.Sort(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
includesRuntimeObjs = true
|
||||
}
|
||||
}
|
||||
|
||||
// we use a NopPositioner when dealing with Table objects
|
||||
// because the objects themselves are not swapped, but rather
|
||||
// the rows in each object are swapped / sorted.
|
||||
r.positioner = &NopPositioner{}
|
||||
|
||||
if includesRuntimeObjs && includesTable {
|
||||
return fmt.Errorf("sorting is not supported on mixed Table and non-Table object lists")
|
||||
}
|
||||
if includesTable {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if not dealing with a Table response from the server, assume
|
||||
// all objects are runtime.Object as usual, and sort using old method.
|
||||
var err error
|
||||
if r.positioner, err = SortObjects(r.decoder, r.objects, r.field); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OriginalPosition returns the original position of a runtime object
|
||||
func (r *RuntimeSorter) OriginalPosition(ix int) int {
|
||||
if r.positioner == nil {
|
||||
return 0
|
||||
}
|
||||
return r.positioner.OriginalPosition(ix)
|
||||
}
|
||||
|
||||
// WithDecoder allows custom decoder to be set for testing
|
||||
func (r *RuntimeSorter) WithDecoder(decoder runtime.Decoder) *RuntimeSorter {
|
||||
r.decoder = decoder
|
||||
return r
|
||||
}
|
||||
|
||||
// NewRuntimeSorter returns a new instance of RuntimeSorter
|
||||
func NewRuntimeSorter(objects []runtime.Object, sortBy string) *RuntimeSorter {
|
||||
parsedField, err := RelaxedJSONPathExpression(sortBy)
|
||||
if err != nil {
|
||||
parsedField = sortBy
|
||||
}
|
||||
|
||||
return &RuntimeSorter{
|
||||
field: parsedField,
|
||||
decoder: kubernetesscheme.Codecs.UniversalDecoder(),
|
||||
objects: objects,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *GetOptions) transformRequests(req *rest.Request) {
|
||||
// We need full objects if printing with openapi columns
|
||||
if o.PrintWithOpenAPICols {
|
||||
return
|
||||
}
|
||||
if !o.ServerPrint || !o.IsHumanReadablePrinter {
|
||||
return
|
||||
}
|
||||
|
||||
group := metav1beta1.GroupName
|
||||
version := metav1beta1.SchemeGroupVersion.Version
|
||||
|
||||
tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
|
||||
req.SetHeader("Accept", tableParam)
|
||||
|
||||
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
|
||||
if o.Sort {
|
||||
req.Param("includeObject", "Object")
|
||||
}
|
||||
}
|
||||
|
||||
// Run performs the get operation.
|
||||
// TODO: remove the need to pass these arguments, like other commands.
|
||||
func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
if len(o.Raw) > 0 {
|
||||
restClient, err := f.RESTClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rawhttp.RawGet(restClient, o.IOStreams, o.Raw)
|
||||
}
|
||||
if o.Watch || o.WatchOnly {
|
||||
return o.watch(f, cmd, args)
|
||||
}
|
||||
|
||||
chunkSize := o.ChunkSize
|
||||
if o.Sort {
|
||||
// TODO(juanvallejo): in the future, we could have the client use chunking
|
||||
// to gather all results, then sort them all at the end to reduce server load.
|
||||
chunkSize = 0
|
||||
}
|
||||
|
||||
r := f.NewBuilder().
|
||||
Unstructured().
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
|
||||
FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
|
||||
LabelSelectorParam(o.LabelSelector).
|
||||
FieldSelectorParam(o.FieldSelector).
|
||||
ExportParam(o.Export).
|
||||
RequestChunksOf(chunkSize).
|
||||
ResourceTypeOrNameArgs(true, args...).
|
||||
ContinueOnError().
|
||||
Latest().
|
||||
Flatten().
|
||||
TransformRequests(o.transformRequests).
|
||||
Do()
|
||||
|
||||
if o.IgnoreNotFound {
|
||||
r.IgnoreErrors(kapierrors.IsNotFound)
|
||||
}
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !o.IsHumanReadablePrinter {
|
||||
return o.printGeneric(r)
|
||||
}
|
||||
|
||||
allErrs := []error{}
|
||||
errs := sets.NewString()
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
printWithKind := multipleGVKsRequested(infos)
|
||||
|
||||
objs := make([]runtime.Object, len(infos))
|
||||
for ix := range infos {
|
||||
objs[ix] = infos[ix].Object
|
||||
}
|
||||
|
||||
sorting, err := cmd.Flags().GetString("sort-by")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var positioner OriginalPositioner
|
||||
if o.Sort {
|
||||
sorter := NewRuntimeSorter(objs, sorting)
|
||||
if err := sorter.Sort(); err != nil {
|
||||
return err
|
||||
}
|
||||
positioner = sorter
|
||||
}
|
||||
|
||||
var printer printers.ResourcePrinter
|
||||
var lastMapping *meta.RESTMapping
|
||||
|
||||
// track if we write any output
|
||||
trackingWriter := &trackingWriterWrapper{Delegate: o.Out}
|
||||
// output an empty line separating output
|
||||
separatorWriter := &separatorWriterWrapper{Delegate: trackingWriter}
|
||||
|
||||
w := printers.GetNewTabWriter(separatorWriter)
|
||||
for ix := range objs {
|
||||
var mapping *meta.RESTMapping
|
||||
var info *resource.Info
|
||||
if positioner != nil {
|
||||
info = infos[positioner.OriginalPosition(ix)]
|
||||
mapping = info.Mapping
|
||||
} else {
|
||||
info = infos[ix]
|
||||
mapping = info.Mapping
|
||||
}
|
||||
|
||||
printWithNamespace := o.AllNamespaces
|
||||
|
||||
if mapping != nil && mapping.Scope.Name() == meta.RESTScopeNameRoot {
|
||||
printWithNamespace = false
|
||||
}
|
||||
|
||||
if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) {
|
||||
w.Flush()
|
||||
w.SetRememberedWidths(nil)
|
||||
|
||||
// add linebreaks between resource groups (if there is more than one)
|
||||
// when it satisfies all following 3 conditions:
|
||||
// 1) it's not the first resource group
|
||||
// 2) it has row header
|
||||
// 3) we've written output since the last time we started a new set of headers
|
||||
if lastMapping != nil && !o.NoHeaders && trackingWriter.Written > 0 {
|
||||
separatorWriter.SetReady(true)
|
||||
}
|
||||
|
||||
printer, err = o.ToPrinter(mapping, nil, printWithNamespace, printWithKind)
|
||||
if err != nil {
|
||||
if !errs.Has(err.Error()) {
|
||||
errs.Insert(err.Error())
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
lastMapping = mapping
|
||||
}
|
||||
|
||||
// ensure a versioned object is passed to the custom-columns printer
|
||||
// if we are using OpenAPI columns to print
|
||||
if o.PrintWithOpenAPICols {
|
||||
printer.PrintObj(info.Object, w)
|
||||
continue
|
||||
}
|
||||
|
||||
printer.PrintObj(info.Object, w)
|
||||
}
|
||||
w.Flush()
|
||||
if trackingWriter.Written == 0 && !o.IgnoreNotFound && len(allErrs) == 0 {
|
||||
// if we wrote no output, and had no errors, and are not ignoring NotFound, be sure we output something
|
||||
if !o.AllNamespaces {
|
||||
fmt.Fprintln(o.ErrOut, fmt.Sprintf("No resources found in %s namespace.", o.Namespace))
|
||||
} else {
|
||||
fmt.Fprintln(o.ErrOut, fmt.Sprintf("No resources found"))
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
type trackingWriterWrapper struct {
|
||||
Delegate io.Writer
|
||||
Written int
|
||||
}
|
||||
|
||||
func (t *trackingWriterWrapper) Write(p []byte) (n int, err error) {
|
||||
t.Written += len(p)
|
||||
return t.Delegate.Write(p)
|
||||
}
|
||||
|
||||
type separatorWriterWrapper struct {
|
||||
Delegate io.Writer
|
||||
Ready bool
|
||||
}
|
||||
|
||||
func (s *separatorWriterWrapper) Write(p []byte) (n int, err error) {
|
||||
// If we're about to write non-empty bytes and `s` is ready,
|
||||
// we prepend an empty line to `p` and reset `s.Read`.
|
||||
if len(p) != 0 && s.Ready {
|
||||
fmt.Fprintln(s.Delegate)
|
||||
s.Ready = false
|
||||
}
|
||||
return s.Delegate.Write(p)
|
||||
}
|
||||
|
||||
func (s *separatorWriterWrapper) SetReady(state bool) {
|
||||
s.Ready = state
|
||||
}
|
||||
|
||||
// watch starts a client-side watch of one or more resources.
|
||||
// TODO: remove the need for arguments here.
|
||||
func (o *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
r := f.NewBuilder().
|
||||
Unstructured().
|
||||
NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
|
||||
FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
|
||||
LabelSelectorParam(o.LabelSelector).
|
||||
FieldSelectorParam(o.FieldSelector).
|
||||
ExportParam(o.Export).
|
||||
RequestChunksOf(o.ChunkSize).
|
||||
ResourceTypeOrNameArgs(true, args...).
|
||||
SingleResourceType().
|
||||
Latest().
|
||||
TransformRequests(o.transformRequests).
|
||||
Do()
|
||||
if err := r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if multipleGVKsRequested(infos) {
|
||||
return i18n.Errorf("watch is only supported on individual resources and resource collections - more than 1 resource was found")
|
||||
}
|
||||
|
||||
info := infos[0]
|
||||
mapping := info.ResourceMapping()
|
||||
outputObjects := utilpointer.BoolPtr(!o.WatchOnly)
|
||||
printer, err := o.ToPrinter(mapping, outputObjects, o.AllNamespaces, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj, err := r.Object()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// watching from resourceVersion 0, starts the watch at ~now and
|
||||
// will return an initial watch event. Starting form ~now, rather
|
||||
// the rv of the object will insure that we start the watch from
|
||||
// inside the watch window, which the rv of the object might not be.
|
||||
rv := "0"
|
||||
isList := meta.IsListType(obj)
|
||||
if isList {
|
||||
// the resourceVersion of list objects is ~now but won't return
|
||||
// an initial watch event
|
||||
rv, err = meta.NewAccessor().ResourceVersion(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
writer := printers.GetNewTabWriter(o.Out)
|
||||
|
||||
// print the current object
|
||||
var objsToPrint []runtime.Object
|
||||
if isList {
|
||||
objsToPrint, _ = meta.ExtractList(obj)
|
||||
} else {
|
||||
objsToPrint = append(objsToPrint, obj)
|
||||
}
|
||||
for _, objToPrint := range objsToPrint {
|
||||
if o.OutputWatchEvents {
|
||||
objToPrint = &metav1.WatchEvent{Type: string(watch.Added), Object: runtime.RawExtension{Object: objToPrint}}
|
||||
}
|
||||
if err := printer.PrintObj(objToPrint, writer); err != nil {
|
||||
return fmt.Errorf("unable to output the provided object: %v", err)
|
||||
}
|
||||
}
|
||||
writer.Flush()
|
||||
if isList {
|
||||
// we can start outputting objects now, watches started from lists don't emit synthetic added events
|
||||
*outputObjects = true
|
||||
} else {
|
||||
// suppress output, since watches started for individual items emit a synthetic ADDED event first
|
||||
*outputObjects = false
|
||||
}
|
||||
|
||||
// print watched changes
|
||||
w, err := r.Watch(rv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
intr := interrupt.New(nil, cancel)
|
||||
intr.Run(func() error {
|
||||
_, err := watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) {
|
||||
objToPrint := e.Object
|
||||
if o.OutputWatchEvents {
|
||||
objToPrint = &metav1.WatchEvent{Type: string(e.Type), Object: runtime.RawExtension{Object: objToPrint}}
|
||||
}
|
||||
if err := printer.PrintObj(objToPrint, writer); err != nil {
|
||||
return false, err
|
||||
}
|
||||
writer.Flush()
|
||||
// after processing at least one event, start outputting objects
|
||||
*outputObjects = true
|
||||
return false, nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *GetOptions) printGeneric(r *resource.Result) error {
|
||||
// we flattened the data from the builder, so we have individual items, but now we'd like to either:
|
||||
// 1. if there is more than one item, combine them all into a single list
|
||||
// 2. if there is a single item and that item is a list, leave it as its specific list
|
||||
// 3. if there is a single item and it is not a list, leave it as a single item
|
||||
var errs []error
|
||||
singleItemImplied := false
|
||||
infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
|
||||
if err != nil {
|
||||
if singleItemImplied {
|
||||
return err
|
||||
}
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(infos) == 0 && o.IgnoreNotFound {
|
||||
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
|
||||
}
|
||||
|
||||
printer, err := o.ToPrinter(nil, nil, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var obj runtime.Object
|
||||
if !singleItemImplied || len(infos) != 1 {
|
||||
// we have zero or multple items, so coerce all items into a list.
|
||||
// we don't want an *unstructured.Unstructured list yet, as we
|
||||
// may be dealing with non-unstructured objects. Compose all items
|
||||
// into an corev1.List, and then decode using an unstructured scheme.
|
||||
list := corev1.List{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "List",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ListMeta: metav1.ListMeta{},
|
||||
}
|
||||
for _, info := range infos {
|
||||
list.Items = append(list.Items, runtime.RawExtension{Object: info.Object})
|
||||
}
|
||||
|
||||
listData, err := json.Marshal(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, listData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj = converted
|
||||
} else {
|
||||
obj = infos[0].Object
|
||||
}
|
||||
|
||||
isList := meta.IsListType(obj)
|
||||
if isList {
|
||||
items, err := meta.ExtractList(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// take the items and create a new list for display
|
||||
list := &unstructured.UnstructuredList{
|
||||
Object: map[string]interface{}{
|
||||
"kind": "List",
|
||||
"apiVersion": "v1",
|
||||
"metadata": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
if listMeta, err := meta.ListAccessor(obj); err == nil {
|
||||
list.Object["metadata"] = map[string]interface{}{
|
||||
"selfLink": listMeta.GetSelfLink(),
|
||||
"resourceVersion": listMeta.GetResourceVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
list.Items = append(list.Items, *item.(*unstructured.Unstructured))
|
||||
}
|
||||
if err := printer.PrintObj(list, o.Out); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
|
||||
}
|
||||
|
||||
if printErr := printer.PrintObj(obj, o.Out); printErr != nil {
|
||||
errs = append(errs, printErr)
|
||||
}
|
||||
|
||||
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
|
||||
}
|
||||
|
||||
func addOpenAPIPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
|
||||
cmd.Flags().BoolVar(&opt.PrintWithOpenAPICols, useOpenAPIPrintColumnFlagLabel, opt.PrintWithOpenAPICols, "If true, use x-kubernetes-print-column metadata (if present) from the OpenAPI schema for displaying a resource.")
|
||||
cmd.Flags().MarkDeprecated(useOpenAPIPrintColumnFlagLabel, "deprecated in favor of server-side printing")
|
||||
}
|
||||
|
||||
func addServerPrintColumnFlags(cmd *cobra.Command, opt *GetOptions) {
|
||||
cmd.Flags().BoolVar(&opt.ServerPrint, useServerPrintColumns, opt.ServerPrint, "If true, have the server return the appropriate table output. Supports extension APIs and CRDs.")
|
||||
}
|
||||
|
||||
func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool {
|
||||
return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
|
||||
}
|
||||
|
||||
func cmdSpecifiesOutputFmt(cmd *cobra.Command) bool {
|
||||
return cmdutil.GetFlagString(cmd, "output") != ""
|
||||
}
|
||||
|
||||
func multipleGVKsRequested(infos []*resource.Info) bool {
|
||||
if len(infos) < 2 {
|
||||
return false
|
||||
}
|
||||
gvk := infos[0].Mapping.GroupVersionKind
|
||||
for _, info := range infos {
|
||||
if info.Mapping.GroupVersionKind != gvk {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
187
vendor/k8s.io/kubectl/pkg/cmd/get/get_flags.go
generated
vendored
Normal file
187
vendor/k8s.io/kubectl/pkg/cmd/get/get_flags.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
Copyright 2018 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 get
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
)
|
||||
|
||||
// PrintFlags composes common printer flag structs
|
||||
// used in the Get command.
|
||||
type PrintFlags struct {
|
||||
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
|
||||
NamePrintFlags *genericclioptions.NamePrintFlags
|
||||
CustomColumnsFlags *CustomColumnsPrintFlags
|
||||
HumanReadableFlags *HumanPrintFlags
|
||||
TemplateFlags *genericclioptions.KubeTemplatePrintFlags
|
||||
|
||||
NoHeaders *bool
|
||||
OutputFormat *string
|
||||
}
|
||||
|
||||
// SetKind sets the Kind option of humanreadable flags
|
||||
func (f *PrintFlags) SetKind(kind schema.GroupKind) {
|
||||
f.HumanReadableFlags.SetKind(kind)
|
||||
}
|
||||
|
||||
// EnsureWithNamespace ensures that humanreadable flags return
|
||||
// a printer capable of printing with a "namespace" column.
|
||||
func (f *PrintFlags) EnsureWithNamespace() error {
|
||||
return f.HumanReadableFlags.EnsureWithNamespace()
|
||||
}
|
||||
|
||||
// EnsureWithKind ensures that humanreadable flags return
|
||||
// a printer capable of including resource kinds.
|
||||
func (f *PrintFlags) EnsureWithKind() error {
|
||||
return f.HumanReadableFlags.EnsureWithKind()
|
||||
}
|
||||
|
||||
// Copy returns a copy of PrintFlags for mutation
|
||||
func (f *PrintFlags) Copy() PrintFlags {
|
||||
printFlags := *f
|
||||
return printFlags
|
||||
}
|
||||
|
||||
// AllowedFormats is the list of formats in which data can be displayed
|
||||
func (f *PrintFlags) AllowedFormats() []string {
|
||||
formats := f.JSONYamlPrintFlags.AllowedFormats()
|
||||
formats = append(formats, f.NamePrintFlags.AllowedFormats()...)
|
||||
formats = append(formats, f.TemplateFlags.AllowedFormats()...)
|
||||
formats = append(formats, f.CustomColumnsFlags.AllowedFormats()...)
|
||||
formats = append(formats, f.HumanReadableFlags.AllowedFormats()...)
|
||||
return formats
|
||||
}
|
||||
|
||||
// UseOpenAPIColumns modifies the output format, as well as the
|
||||
// "allowMissingKeys" option for template printers, to values
|
||||
// defined in the OpenAPI schema of a resource.
|
||||
func (f *PrintFlags) UseOpenAPIColumns(api openapi.Resources, mapping *meta.RESTMapping) error {
|
||||
// Found openapi metadata for this resource
|
||||
schema := api.LookupResource(mapping.GroupVersionKind)
|
||||
if schema == nil {
|
||||
// Schema not found, return empty columns
|
||||
return nil
|
||||
}
|
||||
|
||||
columns, found := openapi.GetPrintColumns(schema.GetExtensions())
|
||||
if !found {
|
||||
// Extension not found, return empty columns
|
||||
return nil
|
||||
}
|
||||
|
||||
parts := strings.SplitN(columns, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowMissingKeys := true
|
||||
f.OutputFormat = &parts[0]
|
||||
f.TemplateFlags.TemplateArgument = &parts[1]
|
||||
f.TemplateFlags.AllowMissingKeys = &allowMissingKeys
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToPrinter attempts to find a composed set of PrintFlags suitable for
|
||||
// returning a printer based on current flag values.
|
||||
func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
|
||||
outputFormat := ""
|
||||
if f.OutputFormat != nil {
|
||||
outputFormat = *f.OutputFormat
|
||||
}
|
||||
|
||||
noHeaders := false
|
||||
if f.NoHeaders != nil {
|
||||
noHeaders = *f.NoHeaders
|
||||
}
|
||||
f.HumanReadableFlags.NoHeaders = noHeaders
|
||||
f.CustomColumnsFlags.NoHeaders = noHeaders
|
||||
|
||||
// for "get.go" we want to support a --template argument given, even when no --output format is provided
|
||||
if f.TemplateFlags.TemplateArgument != nil && len(*f.TemplateFlags.TemplateArgument) > 0 && len(outputFormat) == 0 {
|
||||
outputFormat = "go-template"
|
||||
}
|
||||
|
||||
if p, err := f.TemplateFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if f.TemplateFlags.TemplateArgument != nil {
|
||||
f.CustomColumnsFlags.TemplateArgument = *f.TemplateFlags.TemplateArgument
|
||||
}
|
||||
|
||||
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if p, err := f.HumanReadableFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if p, err := f.CustomColumnsFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to humanreadable and template printing.
|
||||
func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
|
||||
f.JSONYamlPrintFlags.AddFlags(cmd)
|
||||
f.NamePrintFlags.AddFlags(cmd)
|
||||
f.TemplateFlags.AddFlags(cmd)
|
||||
f.HumanReadableFlags.AddFlags(cmd)
|
||||
f.CustomColumnsFlags.AddFlags(cmd)
|
||||
|
||||
if f.OutputFormat != nil {
|
||||
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
|
||||
}
|
||||
if f.NoHeaders != nil {
|
||||
cmd.Flags().BoolVar(f.NoHeaders, "no-headers", *f.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
|
||||
}
|
||||
}
|
||||
|
||||
// NewGetPrintFlags returns flags associated with humanreadable,
|
||||
// template, and "name" printing, with default values set.
|
||||
func NewGetPrintFlags() *PrintFlags {
|
||||
outputFormat := ""
|
||||
noHeaders := false
|
||||
|
||||
return &PrintFlags{
|
||||
OutputFormat: &outputFormat,
|
||||
NoHeaders: &noHeaders,
|
||||
|
||||
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
|
||||
NamePrintFlags: genericclioptions.NewNamePrintFlags(""),
|
||||
TemplateFlags: genericclioptions.NewKubeTemplatePrintFlags(),
|
||||
|
||||
HumanReadableFlags: NewHumanPrintFlags(),
|
||||
CustomColumnsFlags: NewCustomColumnsPrintFlags(),
|
||||
}
|
||||
}
|
||||
138
vendor/k8s.io/kubectl/pkg/cmd/get/humanreadable_flags.go
generated
vendored
Normal file
138
vendor/k8s.io/kubectl/pkg/cmd/get/humanreadable_flags.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright 2018 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 get
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
)
|
||||
|
||||
// HumanPrintFlags provides default flags necessary for printing.
|
||||
// Given the following flag values, a printer can be requested that knows
|
||||
// how to handle printing based on these values.
|
||||
type HumanPrintFlags struct {
|
||||
ShowKind *bool
|
||||
ShowLabels *bool
|
||||
SortBy *string
|
||||
ColumnLabels *[]string
|
||||
|
||||
// get.go-specific values
|
||||
NoHeaders bool
|
||||
|
||||
Kind schema.GroupKind
|
||||
WithNamespace bool
|
||||
}
|
||||
|
||||
// SetKind sets the Kind option
|
||||
func (f *HumanPrintFlags) SetKind(kind schema.GroupKind) {
|
||||
f.Kind = kind
|
||||
}
|
||||
|
||||
// EnsureWithKind sets the "Showkind" humanreadable option to true.
|
||||
func (f *HumanPrintFlags) EnsureWithKind() error {
|
||||
showKind := true
|
||||
f.ShowKind = &showKind
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureWithNamespace sets the "WithNamespace" humanreadable option to true.
|
||||
func (f *HumanPrintFlags) EnsureWithNamespace() error {
|
||||
f.WithNamespace = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllowedFormats returns more customized formating options
|
||||
func (f *HumanPrintFlags) AllowedFormats() []string {
|
||||
return []string{"wide"}
|
||||
}
|
||||
|
||||
// ToPrinter receives an outputFormat and returns a printer capable of
|
||||
// handling human-readable output.
|
||||
func (f *HumanPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
|
||||
if len(outputFormat) > 0 && outputFormat != "wide" {
|
||||
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, AllowedFormats: f.AllowedFormats()}
|
||||
}
|
||||
|
||||
showKind := false
|
||||
if f.ShowKind != nil {
|
||||
showKind = *f.ShowKind
|
||||
}
|
||||
|
||||
showLabels := false
|
||||
if f.ShowLabels != nil {
|
||||
showLabels = *f.ShowLabels
|
||||
}
|
||||
|
||||
columnLabels := []string{}
|
||||
if f.ColumnLabels != nil {
|
||||
columnLabels = *f.ColumnLabels
|
||||
}
|
||||
|
||||
p := printers.NewTablePrinter(printers.PrintOptions{
|
||||
Kind: f.Kind,
|
||||
WithKind: showKind,
|
||||
NoHeaders: f.NoHeaders,
|
||||
Wide: outputFormat == "wide",
|
||||
WithNamespace: f.WithNamespace,
|
||||
ColumnLabels: columnLabels,
|
||||
ShowLabels: showLabels,
|
||||
})
|
||||
|
||||
// TODO(juanvallejo): handle sorting here
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// AddFlags receives a *cobra.Command reference and binds
|
||||
// flags related to human-readable printing to it
|
||||
func (f *HumanPrintFlags) AddFlags(c *cobra.Command) {
|
||||
if f.ShowLabels != nil {
|
||||
c.Flags().BoolVar(f.ShowLabels, "show-labels", *f.ShowLabels, "When printing, show all labels as the last column (default hide labels column)")
|
||||
}
|
||||
if f.SortBy != nil {
|
||||
c.Flags().StringVar(f.SortBy, "sort-by", *f.SortBy, "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.")
|
||||
}
|
||||
if f.ColumnLabels != nil {
|
||||
c.Flags().StringSliceVarP(f.ColumnLabels, "label-columns", "L", *f.ColumnLabels, "Accepts a comma separated list of labels that are going to be presented as columns. Names are case-sensitive. You can also use multiple flag options like -L label1 -L label2...")
|
||||
}
|
||||
if f.ShowKind != nil {
|
||||
c.Flags().BoolVar(f.ShowKind, "show-kind", *f.ShowKind, "If present, list the resource type for the requested object(s).")
|
||||
}
|
||||
}
|
||||
|
||||
// NewHumanPrintFlags returns flags associated with
|
||||
// human-readable printing, with default values set.
|
||||
func NewHumanPrintFlags() *HumanPrintFlags {
|
||||
showLabels := false
|
||||
sortBy := ""
|
||||
showKind := false
|
||||
columnLabels := []string{}
|
||||
|
||||
return &HumanPrintFlags{
|
||||
NoHeaders: false,
|
||||
WithNamespace: false,
|
||||
ColumnLabels: &columnLabels,
|
||||
|
||||
Kind: schema.GroupKind{},
|
||||
ShowLabels: &showLabels,
|
||||
SortBy: &sortBy,
|
||||
ShowKind: &showKind,
|
||||
}
|
||||
}
|
||||
48
vendor/k8s.io/kubectl/pkg/cmd/get/skip_printer.go
generated
vendored
Normal file
48
vendor/k8s.io/kubectl/pkg/cmd/get/skip_printer.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 get
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
)
|
||||
|
||||
// skipPrinter allows conditionally suppressing object output via the output field.
|
||||
// table objects are suppressed by setting their Rows to nil (allowing column definitions to propagate to the delegate).
|
||||
// non-table objects are suppressed by not calling the delegate at all.
|
||||
type skipPrinter struct {
|
||||
delegate printers.ResourcePrinter
|
||||
output *bool
|
||||
}
|
||||
|
||||
func (p *skipPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
|
||||
if *p.output {
|
||||
return p.delegate.PrintObj(obj, writer)
|
||||
}
|
||||
|
||||
table, isTable := obj.(*metav1beta1.Table)
|
||||
if !isTable {
|
||||
return nil
|
||||
}
|
||||
|
||||
table = table.DeepCopy()
|
||||
table.Rows = nil
|
||||
return p.delegate.PrintObj(table, writer)
|
||||
}
|
||||
400
vendor/k8s.io/kubectl/pkg/cmd/get/sorter.go
generated
vendored
Normal file
400
vendor/k8s.io/kubectl/pkg/cmd/get/sorter.go
generated
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
Copyright 2014 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 get
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
"k8s.io/utils/integer"
|
||||
|
||||
"vbom.ml/util/sortorder"
|
||||
)
|
||||
|
||||
// SortingPrinter sorts list types before delegating to another printer.
|
||||
// Non-list types are simply passed through
|
||||
type SortingPrinter struct {
|
||||
SortField string
|
||||
Delegate printers.ResourcePrinter
|
||||
Decoder runtime.Decoder
|
||||
}
|
||||
|
||||
func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
|
||||
if table, isTable := obj.(*metav1beta1.Table); isTable && len(table.Rows) > 1 {
|
||||
parsedField, err := RelaxedJSONPathExpression(s.SortField)
|
||||
if err != nil {
|
||||
parsedField = s.SortField
|
||||
}
|
||||
|
||||
if sorter, err := NewTableSorter(table, parsedField); err != nil {
|
||||
return err
|
||||
} else if err := sorter.Sort(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Delegate.PrintObj(table, out)
|
||||
}
|
||||
|
||||
if meta.IsListType(obj) {
|
||||
if err := s.sortObj(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Delegate.PrintObj(obj, out)
|
||||
}
|
||||
|
||||
return s.Delegate.PrintObj(obj, out)
|
||||
}
|
||||
|
||||
func (s *SortingPrinter) sortObj(obj runtime.Object) error {
|
||||
objs, err := meta.ExtractList(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(objs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sorter, err := SortObjects(s.Decoder, objs, s.SortField)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch list := obj.(type) {
|
||||
case *corev1.List:
|
||||
outputList := make([]runtime.RawExtension, len(objs))
|
||||
for ix := range objs {
|
||||
outputList[ix] = list.Items[sorter.OriginalPosition(ix)]
|
||||
}
|
||||
list.Items = outputList
|
||||
return nil
|
||||
}
|
||||
return meta.SetList(obj, objs)
|
||||
}
|
||||
|
||||
func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) {
|
||||
for ix := range objs {
|
||||
item := objs[ix]
|
||||
switch u := item.(type) {
|
||||
case *runtime.Unknown:
|
||||
var err error
|
||||
// decode runtime.Unknown to runtime.Unstructured for sorting.
|
||||
// we don't actually want the internal versions of known types.
|
||||
if objs[ix], _, err = decoder.Decode(u.Raw, nil, &unstructured.Unstructured{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
field, err := RelaxedJSONPathExpression(fieldInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parser := jsonpath.New("sorting").AllowMissingKeys(true)
|
||||
if err := parser.Parse(field); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We don't do any model validation here, so we traverse all objects to be sorted
|
||||
// and, if the field is valid to at least one of them, we consider it to be a
|
||||
// valid field; otherwise error out.
|
||||
// Note that this requires empty fields to be considered later, when sorting.
|
||||
var fieldFoundOnce bool
|
||||
for _, obj := range objs {
|
||||
values, err := findJSONPathResults(parser, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values) > 0 && len(values[0]) > 0 {
|
||||
fieldFoundOnce = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !fieldFoundOnce {
|
||||
return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
|
||||
}
|
||||
|
||||
sorter := NewRuntimeSort(field, objs)
|
||||
sort.Sort(sorter)
|
||||
return sorter, nil
|
||||
}
|
||||
|
||||
// RuntimeSort is an implementation of the golang sort interface that knows how to sort
|
||||
// lists of runtime.Object
|
||||
type RuntimeSort struct {
|
||||
field string
|
||||
objs []runtime.Object
|
||||
origPosition []int
|
||||
}
|
||||
|
||||
func NewRuntimeSort(field string, objs []runtime.Object) *RuntimeSort {
|
||||
sorter := &RuntimeSort{field: field, objs: objs, origPosition: make([]int, len(objs))}
|
||||
for ix := range objs {
|
||||
sorter.origPosition[ix] = ix
|
||||
}
|
||||
return sorter
|
||||
}
|
||||
|
||||
func (r *RuntimeSort) Len() int {
|
||||
return len(r.objs)
|
||||
}
|
||||
|
||||
func (r *RuntimeSort) Swap(i, j int) {
|
||||
r.objs[i], r.objs[j] = r.objs[j], r.objs[i]
|
||||
r.origPosition[i], r.origPosition[j] = r.origPosition[j], r.origPosition[i]
|
||||
}
|
||||
|
||||
func isLess(i, j reflect.Value) (bool, error) {
|
||||
switch i.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return i.Int() < j.Int(), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return i.Uint() < j.Uint(), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return i.Float() < j.Float(), nil
|
||||
case reflect.String:
|
||||
return sortorder.NaturalLess(i.String(), j.String()), nil
|
||||
case reflect.Ptr:
|
||||
return isLess(i.Elem(), j.Elem())
|
||||
case reflect.Struct:
|
||||
// sort metav1.Time
|
||||
in := i.Interface()
|
||||
if t, ok := in.(metav1.Time); ok {
|
||||
time := j.Interface().(metav1.Time)
|
||||
return t.Before(&time), nil
|
||||
}
|
||||
// fallback to the fields comparison
|
||||
for idx := 0; idx < i.NumField(); idx++ {
|
||||
less, err := isLess(i.Field(idx), j.Field(idx))
|
||||
if err != nil || !less {
|
||||
return less, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
case reflect.Array, reflect.Slice:
|
||||
// note: the length of i and j may be different
|
||||
for idx := 0; idx < integer.IntMin(i.Len(), j.Len()); idx++ {
|
||||
less, err := isLess(i.Index(idx), j.Index(idx))
|
||||
if err != nil || !less {
|
||||
return less, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
case reflect.Interface:
|
||||
switch itype := i.Interface().(type) {
|
||||
case uint8:
|
||||
if jtype, ok := j.Interface().(uint8); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case uint16:
|
||||
if jtype, ok := j.Interface().(uint16); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case uint32:
|
||||
if jtype, ok := j.Interface().(uint32); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case uint64:
|
||||
if jtype, ok := j.Interface().(uint64); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case int8:
|
||||
if jtype, ok := j.Interface().(int8); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case int16:
|
||||
if jtype, ok := j.Interface().(int16); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case int32:
|
||||
if jtype, ok := j.Interface().(int32); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case int64:
|
||||
if jtype, ok := j.Interface().(int64); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case uint:
|
||||
if jtype, ok := j.Interface().(uint); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case int:
|
||||
if jtype, ok := j.Interface().(int); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case float32:
|
||||
if jtype, ok := j.Interface().(float32); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case float64:
|
||||
if jtype, ok := j.Interface().(float64); ok {
|
||||
return itype < jtype, nil
|
||||
}
|
||||
case string:
|
||||
if jtype, ok := j.Interface().(string); ok {
|
||||
return sortorder.NaturalLess(itype, jtype), nil
|
||||
}
|
||||
default:
|
||||
return false, fmt.Errorf("unsortable type: %T", itype)
|
||||
}
|
||||
return false, fmt.Errorf("unsortable interface: %v", i.Kind())
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("unsortable type: %v", i.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RuntimeSort) Less(i, j int) bool {
|
||||
iObj := r.objs[i]
|
||||
jObj := r.objs[j]
|
||||
|
||||
var iValues [][]reflect.Value
|
||||
var jValues [][]reflect.Value
|
||||
var err error
|
||||
|
||||
parser := jsonpath.New("sorting").AllowMissingKeys(true)
|
||||
err = parser.Parse(r.field)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iValues, err = findJSONPathResults(parser, iObj)
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err)
|
||||
}
|
||||
|
||||
jValues, err = findJSONPathResults(parser, jObj)
|
||||
if err != nil {
|
||||
klog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
|
||||
}
|
||||
|
||||
if len(iValues) == 0 || len(iValues[0]) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(jValues) == 0 || len(jValues[0]) == 0 {
|
||||
return false
|
||||
}
|
||||
iField := iValues[0][0]
|
||||
jField := jValues[0][0]
|
||||
|
||||
less, err := isLess(iField, jField)
|
||||
if err != nil {
|
||||
klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err)
|
||||
}
|
||||
return less
|
||||
}
|
||||
|
||||
// OriginalPosition returns the starting (original) position of a particular index.
|
||||
// e.g. If OriginalPosition(0) returns 5 than the
|
||||
// the item currently at position 0 was at position 5 in the original unsorted array.
|
||||
func (r *RuntimeSort) OriginalPosition(ix int) int {
|
||||
if ix < 0 || ix > len(r.origPosition) {
|
||||
return -1
|
||||
}
|
||||
return r.origPosition[ix]
|
||||
}
|
||||
|
||||
type TableSorter struct {
|
||||
field string
|
||||
obj *metav1beta1.Table
|
||||
parsedRows [][][]reflect.Value
|
||||
}
|
||||
|
||||
func (t *TableSorter) Len() int {
|
||||
return len(t.obj.Rows)
|
||||
}
|
||||
|
||||
func (t *TableSorter) Swap(i, j int) {
|
||||
t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i]
|
||||
t.parsedRows[i], t.parsedRows[j] = t.parsedRows[j], t.parsedRows[i]
|
||||
}
|
||||
|
||||
func (t *TableSorter) Less(i, j int) bool {
|
||||
iValues := t.parsedRows[i]
|
||||
jValues := t.parsedRows[j]
|
||||
|
||||
if len(iValues) == 0 || len(iValues[0]) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(jValues) == 0 || len(jValues[0]) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
iField := iValues[0][0]
|
||||
jField := jValues[0][0]
|
||||
|
||||
less, err := isLess(iField, jField)
|
||||
if err != nil {
|
||||
klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", t.field, t.parsedRows, iField.Kind().String(), err)
|
||||
}
|
||||
return less
|
||||
}
|
||||
|
||||
func (t *TableSorter) Sort() error {
|
||||
sort.Sort(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTableSorter(table *metav1beta1.Table, field string) (*TableSorter, error) {
|
||||
var parsedRows [][][]reflect.Value
|
||||
|
||||
parser := jsonpath.New("sorting").AllowMissingKeys(true)
|
||||
err := parser.Parse(field)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sorting error: %v", err)
|
||||
}
|
||||
|
||||
fieldFoundOnce := false
|
||||
for i := range table.Rows {
|
||||
parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err)
|
||||
}
|
||||
parsedRows = append(parsedRows, parsedRow)
|
||||
if len(parsedRow) > 0 && len(parsedRow[0]) > 0 {
|
||||
fieldFoundOnce = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(table.Rows) > 0 && !fieldFoundOnce {
|
||||
return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
|
||||
}
|
||||
|
||||
return &TableSorter{
|
||||
obj: table,
|
||||
field: field,
|
||||
parsedRows: parsedRows,
|
||||
}, nil
|
||||
}
|
||||
func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) {
|
||||
if unstructuredObj, ok := from.(*unstructured.Unstructured); ok {
|
||||
return parser.FindResults(unstructuredObj.Object)
|
||||
}
|
||||
return parser.FindResults(reflect.ValueOf(from).Elem().Interface())
|
||||
}
|
||||
87
vendor/k8s.io/kubectl/pkg/cmd/get/table_printer.go
generated
vendored
Normal file
87
vendor/k8s.io/kubectl/pkg/cmd/get/table_printer.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
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 get
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/printers"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// TablePrinter decodes table objects into typed objects before delegating to another printer.
|
||||
// Non-table types are simply passed through
|
||||
type TablePrinter struct {
|
||||
Delegate printers.ResourcePrinter
|
||||
}
|
||||
|
||||
func (t *TablePrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
|
||||
table, err := decodeIntoTable(obj)
|
||||
if err == nil {
|
||||
return t.Delegate.PrintObj(table, writer)
|
||||
}
|
||||
// if we are unable to decode server response into a v1beta1.Table,
|
||||
// fallback to client-side printing with whatever info the server returned.
|
||||
klog.V(2).Infof("Unable to decode server response into a Table. Falling back to hardcoded types: %v", err)
|
||||
return t.Delegate.PrintObj(obj, writer)
|
||||
}
|
||||
|
||||
func decodeIntoTable(obj runtime.Object) (runtime.Object, error) {
|
||||
event, isEvent := obj.(*metav1.WatchEvent)
|
||||
if isEvent {
|
||||
obj = event.Object.Object
|
||||
}
|
||||
|
||||
if obj.GetObjectKind().GroupVersionKind().Group != metav1beta1.GroupName {
|
||||
return nil, fmt.Errorf("attempt to decode non-Table object into a v1beta1.Table")
|
||||
}
|
||||
if obj.GetObjectKind().GroupVersionKind().Kind != "Table" {
|
||||
return nil, fmt.Errorf("attempt to decode non-Table object into a v1beta1.Table")
|
||||
}
|
||||
|
||||
unstr, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("attempt to decode non-Unstructured object")
|
||||
}
|
||||
table := &metav1beta1.Table{}
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, table); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range table.Rows {
|
||||
row := &table.Rows[i]
|
||||
if row.Object.Raw == nil || row.Object.Object != nil {
|
||||
continue
|
||||
}
|
||||
converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, row.Object.Raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.Object.Object = converted
|
||||
}
|
||||
|
||||
if isEvent {
|
||||
event.Object.Object = table
|
||||
return event, nil
|
||||
}
|
||||
return table, nil
|
||||
}
|
||||
109
vendor/k8s.io/kubectl/pkg/cmd/util/crdfinder.go
generated
vendored
Normal file
109
vendor/k8s.io/kubectl/pkg/cmd/util/crdfinder.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// CRDGetter is a function that can download the list of GVK for all
|
||||
// CRDs.
|
||||
type CRDGetter func() ([]schema.GroupKind, error)
|
||||
|
||||
func CRDFromDynamic(client dynamic.Interface) CRDGetter {
|
||||
return func() ([]schema.GroupKind, error) {
|
||||
list, err := client.Resource(schema.GroupVersionResource{
|
||||
Group: "apiextensions.k8s.io",
|
||||
Version: "v1beta1",
|
||||
Resource: "customresourcedefinitions",
|
||||
}).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list CRDs: %v", err)
|
||||
}
|
||||
if list == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
gks := []schema.GroupKind{}
|
||||
|
||||
// We need to parse the list to get the gvk, I guess that's fine.
|
||||
for _, crd := range (*list).Items {
|
||||
// Look for group, version, and kind
|
||||
group, _, _ := unstructured.NestedString(crd.Object, "spec", "group")
|
||||
kind, _, _ := unstructured.NestedString(crd.Object, "spec", "names", "kind")
|
||||
|
||||
gks = append(gks, schema.GroupKind{
|
||||
Group: group,
|
||||
Kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
return gks, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CRDFinder keeps a cache of known CRDs and finds a given GVK in the
|
||||
// list.
|
||||
type CRDFinder interface {
|
||||
HasCRD(gvk schema.GroupKind) (bool, error)
|
||||
}
|
||||
|
||||
func NewCRDFinder(getter CRDGetter) CRDFinder {
|
||||
return &crdFinder{
|
||||
getter: getter,
|
||||
}
|
||||
}
|
||||
|
||||
type crdFinder struct {
|
||||
getter CRDGetter
|
||||
cache *[]schema.GroupKind
|
||||
}
|
||||
|
||||
func (f *crdFinder) cacheCRDs() error {
|
||||
if f.cache != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
list, err := f.getter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.cache = &list
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *crdFinder) findCRD(gvk schema.GroupKind) bool {
|
||||
for _, crd := range *f.cache {
|
||||
if reflect.DeepEqual(gvk, crd) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *crdFinder) HasCRD(gvk schema.GroupKind) (bool, error) {
|
||||
if err := f.cacheCRDs(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.findCRD(gvk), nil
|
||||
}
|
||||
66
vendor/k8s.io/kubectl/pkg/cmd/util/factory.go
generated
vendored
Normal file
66
vendor/k8s.io/kubectl/pkg/cmd/util/factory.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2014 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 util
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
"k8s.io/kubectl/pkg/validation"
|
||||
)
|
||||
|
||||
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
||||
// of resources and different API sets.
|
||||
// The rings are here for a reason. In order for composers to be able to provide alternative factory implementations
|
||||
// they need to provide low level pieces of *certain* functions so that when the factory calls back into itself
|
||||
// it uses the custom version of the function. Rather than try to enumerate everything that someone would want to override
|
||||
// we split the factory into rings, where each ring can depend on methods in an earlier ring, but cannot depend
|
||||
// upon peer methods in its own ring.
|
||||
// TODO: make the functions interfaces
|
||||
// TODO: pass the various interfaces on the factory directly into the command constructors (so the
|
||||
// commands are decoupled from the factory).
|
||||
type Factory interface {
|
||||
genericclioptions.RESTClientGetter
|
||||
|
||||
// DynamicClient returns a dynamic client ready for use
|
||||
DynamicClient() (dynamic.Interface, error)
|
||||
|
||||
// KubernetesClientSet gives you back an external clientset
|
||||
KubernetesClientSet() (*kubernetes.Clientset, error)
|
||||
|
||||
// Returns a RESTClient for accessing Kubernetes resources or an error.
|
||||
RESTClient() (*restclient.RESTClient, error)
|
||||
|
||||
// NewBuilder returns an object that assists in loading objects from both disk and the server
|
||||
// and which implements the common patterns for CLI interactions with generic resources.
|
||||
NewBuilder() *resource.Builder
|
||||
|
||||
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
|
||||
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer.
|
||||
ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||
// Returns a RESTClient for working with Unstructured objects.
|
||||
UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||
|
||||
// Returns a schema that can validate objects stored on disk.
|
||||
Validator(validate bool) (validation.Schema, error)
|
||||
// OpenAPISchema returns the schema openapi schema definition
|
||||
OpenAPISchema() (openapi.Resources, error)
|
||||
}
|
||||
177
vendor/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go
generated
vendored
Normal file
177
vendor/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// this file contains factories with no other dependencies
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
openapivalidation "k8s.io/kubectl/pkg/util/openapi/validation"
|
||||
"k8s.io/kubectl/pkg/validation"
|
||||
)
|
||||
|
||||
type factoryImpl struct {
|
||||
clientGetter genericclioptions.RESTClientGetter
|
||||
|
||||
// openAPIGetter loads and caches openapi specs
|
||||
openAPIGetter openAPIGetter
|
||||
}
|
||||
|
||||
type openAPIGetter struct {
|
||||
once sync.Once
|
||||
getter openapi.Getter
|
||||
}
|
||||
|
||||
func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
|
||||
if clientGetter == nil {
|
||||
panic("attempt to instantiate client_access_factory with nil clientGetter")
|
||||
}
|
||||
|
||||
f := &factoryImpl{
|
||||
clientGetter: clientGetter,
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *factoryImpl) ToRESTConfig() (*restclient.Config, error) {
|
||||
return f.clientGetter.ToRESTConfig()
|
||||
}
|
||||
|
||||
func (f *factoryImpl) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
return f.clientGetter.ToRESTMapper()
|
||||
}
|
||||
|
||||
func (f *factoryImpl) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
return f.clientGetter.ToDiscoveryClient()
|
||||
}
|
||||
|
||||
func (f *factoryImpl) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
return f.clientGetter.ToRawKubeConfigLoader()
|
||||
}
|
||||
|
||||
func (f *factoryImpl) KubernetesClientSet() (*kubernetes.Clientset, error) {
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kubernetes.NewForConfig(clientConfig)
|
||||
}
|
||||
|
||||
func (f *factoryImpl) DynamicClient() (dynamic.Interface, error) {
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dynamic.NewForConfig(clientConfig)
|
||||
}
|
||||
|
||||
// NewBuilder returns a new resource builder for structured api objects.
|
||||
func (f *factoryImpl) NewBuilder() *resource.Builder {
|
||||
return resource.NewBuilder(f.clientGetter)
|
||||
}
|
||||
|
||||
func (f *factoryImpl) RESTClient() (*restclient.RESTClient, error) {
|
||||
clientConfig, err := f.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setKubernetesDefaults(clientConfig)
|
||||
return restclient.RESTClientFor(clientConfig)
|
||||
}
|
||||
|
||||
func (f *factoryImpl) ClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
|
||||
cfg, err := f.clientGetter.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setKubernetesDefaults(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvk := mapping.GroupVersionKind
|
||||
switch gvk.Group {
|
||||
case corev1.GroupName:
|
||||
cfg.APIPath = "/api"
|
||||
default:
|
||||
cfg.APIPath = "/apis"
|
||||
}
|
||||
gv := gvk.GroupVersion()
|
||||
cfg.GroupVersion = &gv
|
||||
return restclient.RESTClientFor(cfg)
|
||||
}
|
||||
|
||||
func (f *factoryImpl) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) {
|
||||
cfg, err := f.clientGetter.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := restclient.SetKubernetesDefaults(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.APIPath = "/apis"
|
||||
if mapping.GroupVersionKind.Group == corev1.GroupName {
|
||||
cfg.APIPath = "/api"
|
||||
}
|
||||
gv := mapping.GroupVersionKind.GroupVersion()
|
||||
cfg.ContentConfig = resource.UnstructuredPlusDefaultContentConfig()
|
||||
cfg.GroupVersion = &gv
|
||||
return restclient.RESTClientFor(cfg)
|
||||
}
|
||||
|
||||
func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) {
|
||||
if !validate {
|
||||
return validation.NullSchema{}, nil
|
||||
}
|
||||
|
||||
resources, err := f.OpenAPISchema()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return validation.ConjunctiveSchema{
|
||||
openapivalidation.NewSchemaValidation(resources),
|
||||
validation.NoDoubleKeySchema{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OpenAPISchema returns metadata and structural information about Kubernetes object definitions.
|
||||
func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) {
|
||||
discovery, err := f.clientGetter.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Lazily initialize the OpenAPIGetter once
|
||||
f.openAPIGetter.once.Do(func() {
|
||||
// Create the caching OpenAPIGetter
|
||||
f.openAPIGetter.getter = openapi.NewOpenAPIGetter(discovery)
|
||||
})
|
||||
|
||||
// Delegate to the OpenAPIGetter
|
||||
return f.openAPIGetter.getter.Get()
|
||||
}
|
||||
661
vendor/k8s.io/kubectl/pkg/cmd/util/helpers.go
generated
vendored
Normal file
661
vendor/k8s.io/kubectl/pkg/cmd/util/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,661 @@
|
||||
/*
|
||||
Copyright 2014 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 util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/scale"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/klog"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
ApplyAnnotationsFlag = "save-config"
|
||||
DefaultErrorExitCode = 1
|
||||
)
|
||||
|
||||
type debugError interface {
|
||||
DebugError() (msg string, args []interface{})
|
||||
}
|
||||
|
||||
// AddSourceToErr adds handleResourcePrefix and source string to error message.
|
||||
// verb is the string like "creating", "deleting" etc.
|
||||
// source is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
|
||||
func AddSourceToErr(verb string, source string, err error) error {
|
||||
if source != "" {
|
||||
if statusError, ok := err.(kerrors.APIStatus); ok {
|
||||
status := statusError.Status()
|
||||
status.Message = fmt.Sprintf("error when %s %q: %v", verb, source, status.Message)
|
||||
return &kerrors.StatusError{ErrStatus: status}
|
||||
}
|
||||
return fmt.Errorf("error when %s %q: %v", verb, source, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var fatalErrHandler = fatal
|
||||
|
||||
// BehaviorOnFatal allows you to override the default behavior when a fatal
|
||||
// error occurs, which is to call os.Exit(code). You can pass 'panic' as a function
|
||||
// here if you prefer the panic() over os.Exit(1).
|
||||
func BehaviorOnFatal(f func(string, int)) {
|
||||
fatalErrHandler = f
|
||||
}
|
||||
|
||||
// DefaultBehaviorOnFatal allows you to undo any previous override. Useful in
|
||||
// tests.
|
||||
func DefaultBehaviorOnFatal() {
|
||||
fatalErrHandler = fatal
|
||||
}
|
||||
|
||||
// fatal prints the message (if provided) and then exits. If V(2) or greater,
|
||||
// klog.Fatal is invoked for extended information.
|
||||
func fatal(msg string, code int) {
|
||||
if klog.V(2) {
|
||||
klog.FatalDepth(2, msg)
|
||||
}
|
||||
if len(msg) > 0 {
|
||||
// add newline if needed
|
||||
if !strings.HasSuffix(msg, "\n") {
|
||||
msg += "\n"
|
||||
}
|
||||
fmt.Fprint(os.Stderr, msg)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// ErrExit may be passed to CheckError to instruct it to output nothing but exit with
|
||||
// status code 1.
|
||||
var ErrExit = fmt.Errorf("exit")
|
||||
|
||||
// CheckErr prints a user friendly error to STDERR and exits with a non-zero
|
||||
// exit code. Unrecognized errors will be printed with an "error: " prefix.
|
||||
//
|
||||
// This method is generic to the command in use and may be used by non-Kubectl
|
||||
// commands.
|
||||
func CheckErr(err error) {
|
||||
checkErr(err, fatalErrHandler)
|
||||
}
|
||||
|
||||
// checkErr formats a given error as a string and calls the passed handleErr
|
||||
// func with that string and an kubectl exit code.
|
||||
func checkErr(err error, handleErr func(string, int)) {
|
||||
// unwrap aggregates of 1
|
||||
if agg, ok := err.(utilerrors.Aggregate); ok && len(agg.Errors()) == 1 {
|
||||
err = agg.Errors()[0]
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case err == ErrExit:
|
||||
handleErr("", DefaultErrorExitCode)
|
||||
case kerrors.IsInvalid(err):
|
||||
details := err.(*kerrors.StatusError).Status().Details
|
||||
s := "The request is invalid"
|
||||
if details == nil {
|
||||
handleErr(s, DefaultErrorExitCode)
|
||||
return
|
||||
}
|
||||
if len(details.Kind) != 0 || len(details.Name) != 0 {
|
||||
s = fmt.Sprintf("The %s %q is invalid", details.Kind, details.Name)
|
||||
}
|
||||
if len(details.Causes) > 0 {
|
||||
errs := statusCausesToAggrError(details.Causes)
|
||||
handleErr(MultilineError(s+": ", errs), DefaultErrorExitCode)
|
||||
} else {
|
||||
handleErr(s, DefaultErrorExitCode)
|
||||
}
|
||||
case clientcmd.IsConfigurationInvalid(err):
|
||||
handleErr(MultilineError("Error in configuration: ", err), DefaultErrorExitCode)
|
||||
default:
|
||||
switch err := err.(type) {
|
||||
case *meta.NoResourceMatchError:
|
||||
switch {
|
||||
case len(err.PartialResource.Group) > 0 && len(err.PartialResource.Version) > 0:
|
||||
handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q and version %q", err.PartialResource.Resource, err.PartialResource.Group, err.PartialResource.Version), DefaultErrorExitCode)
|
||||
case len(err.PartialResource.Group) > 0:
|
||||
handleErr(fmt.Sprintf("the server doesn't have a resource type %q in group %q", err.PartialResource.Resource, err.PartialResource.Group), DefaultErrorExitCode)
|
||||
case len(err.PartialResource.Version) > 0:
|
||||
handleErr(fmt.Sprintf("the server doesn't have a resource type %q in version %q", err.PartialResource.Resource, err.PartialResource.Version), DefaultErrorExitCode)
|
||||
default:
|
||||
handleErr(fmt.Sprintf("the server doesn't have a resource type %q", err.PartialResource.Resource), DefaultErrorExitCode)
|
||||
}
|
||||
case utilerrors.Aggregate:
|
||||
handleErr(MultipleErrors(``, err.Errors()), DefaultErrorExitCode)
|
||||
case utilexec.ExitError:
|
||||
handleErr(err.Error(), err.ExitStatus())
|
||||
default: // for any other error type
|
||||
msg, ok := StandardErrorMessage(err)
|
||||
if !ok {
|
||||
msg = err.Error()
|
||||
if !strings.HasPrefix(msg, "error: ") {
|
||||
msg = fmt.Sprintf("error: %s", msg)
|
||||
}
|
||||
}
|
||||
handleErr(msg, DefaultErrorExitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func statusCausesToAggrError(scs []metav1.StatusCause) utilerrors.Aggregate {
|
||||
errs := make([]error, 0, len(scs))
|
||||
errorMsgs := sets.NewString()
|
||||
for _, sc := range scs {
|
||||
// check for duplicate error messages and skip them
|
||||
msg := fmt.Sprintf("%s: %s", sc.Field, sc.Message)
|
||||
if errorMsgs.Has(msg) {
|
||||
continue
|
||||
}
|
||||
errorMsgs.Insert(msg)
|
||||
errs = append(errs, errors.New(msg))
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// StandardErrorMessage translates common errors into a human readable message, or returns
|
||||
// false if the error is not one of the recognized types. It may also log extended
|
||||
// information to klog.
|
||||
//
|
||||
// This method is generic to the command in use and may be used by non-Kubectl
|
||||
// commands.
|
||||
func StandardErrorMessage(err error) (string, bool) {
|
||||
if debugErr, ok := err.(debugError); ok {
|
||||
klog.V(4).Infof(debugErr.DebugError())
|
||||
}
|
||||
status, isStatus := err.(kerrors.APIStatus)
|
||||
switch {
|
||||
case isStatus:
|
||||
switch s := status.Status(); {
|
||||
case s.Reason == metav1.StatusReasonUnauthorized:
|
||||
return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true
|
||||
case len(s.Reason) > 0:
|
||||
return fmt.Sprintf("Error from server (%s): %s", s.Reason, err.Error()), true
|
||||
default:
|
||||
return fmt.Sprintf("Error from server: %s", err.Error()), true
|
||||
}
|
||||
case kerrors.IsUnexpectedObjectError(err):
|
||||
return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true
|
||||
}
|
||||
switch t := err.(type) {
|
||||
case *url.Error:
|
||||
klog.V(4).Infof("Connection error: %s %s: %v", t.Op, t.URL, t.Err)
|
||||
switch {
|
||||
case strings.Contains(t.Err.Error(), "connection refused"):
|
||||
host := t.URL
|
||||
if server, err := url.Parse(t.URL); err == nil {
|
||||
host = server.Host
|
||||
}
|
||||
return fmt.Sprintf("The connection to the server %s was refused - did you specify the right host or port?", host), true
|
||||
}
|
||||
return fmt.Sprintf("Unable to connect to the server: %v", t.Err), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// MultilineError returns a string representing an error that splits sub errors into their own
|
||||
// lines. The returned string will end with a newline.
|
||||
func MultilineError(prefix string, err error) string {
|
||||
if agg, ok := err.(utilerrors.Aggregate); ok {
|
||||
errs := utilerrors.Flatten(agg).Errors()
|
||||
buf := &bytes.Buffer{}
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
return fmt.Sprintf("%s%v\n", prefix, err)
|
||||
case 1:
|
||||
return fmt.Sprintf("%s%v\n", prefix, messageForError(errs[0]))
|
||||
default:
|
||||
fmt.Fprintln(buf, prefix)
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(buf, "* %v\n", messageForError(err))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s%s\n", prefix, err)
|
||||
}
|
||||
|
||||
// PrintErrorWithCauses prints an error's kind, name, and each of the error's causes in a new line.
|
||||
// The returned string will end with a newline.
|
||||
// Returns true if a case exists to handle the error type, or false otherwise.
|
||||
func PrintErrorWithCauses(err error, errOut io.Writer) bool {
|
||||
switch t := err.(type) {
|
||||
case *kerrors.StatusError:
|
||||
errorDetails := t.Status().Details
|
||||
if errorDetails != nil {
|
||||
fmt.Fprintf(errOut, "error: %s %q is invalid\n\n", errorDetails.Kind, errorDetails.Name)
|
||||
for _, cause := range errorDetails.Causes {
|
||||
fmt.Fprintf(errOut, "* %s: %s\n", cause.Field, cause.Message)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(errOut, "error: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// MultipleErrors returns a newline delimited string containing
|
||||
// the prefix and referenced errors in standard form.
|
||||
func MultipleErrors(prefix string, errs []error) string {
|
||||
buf := &bytes.Buffer{}
|
||||
for _, err := range errs {
|
||||
fmt.Fprintf(buf, "%s%v\n", prefix, messageForError(err))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// messageForError returns the string representing the error.
|
||||
func messageForError(err error) string {
|
||||
msg, ok := StandardErrorMessage(err)
|
||||
if !ok {
|
||||
msg = err.Error()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func UsageErrorf(cmd *cobra.Command, format string, args ...interface{}) error {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath())
|
||||
}
|
||||
|
||||
func IsFilenameSliceEmpty(filenames []string, directory string) bool {
|
||||
return len(filenames) == 0 && directory == ""
|
||||
}
|
||||
|
||||
func GetFlagString(cmd *cobra.Command, flag string) string {
|
||||
s, err := cmd.Flags().GetString(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// GetFlagStringSlice can be used to accept multiple argument with flag repetition (e.g. -f arg1,arg2 -f arg3 ...)
|
||||
func GetFlagStringSlice(cmd *cobra.Command, flag string) []string {
|
||||
s, err := cmd.Flags().GetStringSlice(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// GetFlagStringArray can be used to accept multiple argument with flag repetition (e.g. -f arg1 -f arg2 ...)
|
||||
func GetFlagStringArray(cmd *cobra.Command, flag string) []string {
|
||||
s, err := cmd.Flags().GetStringArray(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func GetFlagBool(cmd *cobra.Command, flag string) bool {
|
||||
b, err := cmd.Flags().GetBool(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Assumes the flag has a default value.
|
||||
func GetFlagInt(cmd *cobra.Command, flag string) int {
|
||||
i, err := cmd.Flags().GetInt(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Assumes the flag has a default value.
|
||||
func GetFlagInt32(cmd *cobra.Command, flag string) int32 {
|
||||
i, err := cmd.Flags().GetInt32(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Assumes the flag has a default value.
|
||||
func GetFlagInt64(cmd *cobra.Command, flag string) int64 {
|
||||
i, err := cmd.Flags().GetInt64(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration {
|
||||
d, err := cmd.Flags().GetDuration(flag)
|
||||
if err != nil {
|
||||
klog.Fatalf("error accessing flag %s for command %s: %v", flag, cmd.Name(), err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
|
||||
timeout := GetFlagDuration(cmd, "pod-running-timeout")
|
||||
if timeout <= 0 {
|
||||
return timeout, fmt.Errorf("--pod-running-timeout must be higher than zero")
|
||||
}
|
||||
return timeout, nil
|
||||
}
|
||||
|
||||
func AddValidateFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it")
|
||||
}
|
||||
|
||||
func AddValidateOptionFlags(cmd *cobra.Command, options *ValidateOptions) {
|
||||
cmd.Flags().BoolVar(&options.EnableValidation, "validate", options.EnableValidation, "If true, use a schema to validate the input before sending it")
|
||||
}
|
||||
|
||||
func AddFilenameOptionFlags(cmd *cobra.Command, options *resource.FilenameOptions, usage string) {
|
||||
AddJsonFilenameFlag(cmd.Flags(), &options.Filenames, "Filename, directory, or URL to files "+usage)
|
||||
AddKustomizeFlag(cmd.Flags(), &options.Kustomize)
|
||||
cmd.Flags().BoolVarP(&options.Recursive, "recursive", "R", options.Recursive, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
|
||||
}
|
||||
|
||||
func AddJsonFilenameFlag(flags *pflag.FlagSet, value *[]string, usage string) {
|
||||
flags.StringSliceVarP(value, "filename", "f", *value, usage)
|
||||
annotations := make([]string, 0, len(resource.FileExtensions))
|
||||
for _, ext := range resource.FileExtensions {
|
||||
annotations = append(annotations, strings.TrimLeft(ext, "."))
|
||||
}
|
||||
flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations)
|
||||
}
|
||||
|
||||
// AddKustomizeFlag adds kustomize flag to a command
|
||||
func AddKustomizeFlag(flags *pflag.FlagSet, value *string) {
|
||||
flags.StringVarP(value, "kustomize", "k", *value, "Process the kustomization directory. This flag can't be used together with -f or -R.")
|
||||
}
|
||||
|
||||
// AddDryRunFlag adds dry-run flag to a command. Usually used by mutations.
|
||||
func AddDryRunFlag(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
|
||||
}
|
||||
|
||||
func AddServerSideApplyFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("server-side", false, "If true, apply runs in the server instead of the client.")
|
||||
cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts.")
|
||||
cmd.Flags().String("field-manager", "kubectl", "Name of the manager used to track field ownership.")
|
||||
}
|
||||
|
||||
func AddPodRunningTimeoutFlag(cmd *cobra.Command, defaultTimeout time.Duration) {
|
||||
cmd.Flags().Duration("pod-running-timeout", defaultTimeout, "The length of time (like 5s, 2m, or 3h, higher than zero) to wait until at least one pod is running")
|
||||
}
|
||||
|
||||
func AddApplyAnnotationFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool(ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
|
||||
}
|
||||
|
||||
func AddApplyAnnotationVarFlags(cmd *cobra.Command, applyAnnotation *bool) {
|
||||
cmd.Flags().BoolVar(applyAnnotation, ApplyAnnotationsFlag, *applyAnnotation, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
|
||||
}
|
||||
|
||||
// AddGeneratorFlags adds flags common to resource generation commands
|
||||
// TODO: need to take a pass at other generator commands to use this set of flags
|
||||
func AddGeneratorFlags(cmd *cobra.Command, defaultGenerator string) {
|
||||
cmd.Flags().String("generator", defaultGenerator, "The name of the API generator to use.")
|
||||
AddDryRunFlag(cmd)
|
||||
}
|
||||
|
||||
type ValidateOptions struct {
|
||||
EnableValidation bool
|
||||
}
|
||||
|
||||
// Merge requires JSON serialization
|
||||
// TODO: merge assumes JSON serialization, and does not properly abstract API retrieval
|
||||
func Merge(codec runtime.Codec, dst runtime.Object, fragment string) (runtime.Object, error) {
|
||||
// encode dst into versioned json and apply fragment directly too it
|
||||
target, err := runtime.Encode(codec, dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patched, err := jsonpatch.MergePatch(target, []byte(fragment))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := runtime.Decode(codec, patched)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DumpReaderToFile writes all data from the given io.Reader to the specified file
|
||||
// (usually for temporary use).
|
||||
func DumpReaderToFile(reader io.Reader, filename string) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
count, err := reader.Read(buffer)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write(buffer[:count])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetServerSideApplyFlag(cmd *cobra.Command) bool {
|
||||
return GetFlagBool(cmd, "server-side")
|
||||
}
|
||||
|
||||
func GetForceConflictsFlag(cmd *cobra.Command) bool {
|
||||
return GetFlagBool(cmd, "force-conflicts")
|
||||
}
|
||||
|
||||
func GetFieldManagerFlag(cmd *cobra.Command) string {
|
||||
return GetFlagString(cmd, "field-manager")
|
||||
}
|
||||
|
||||
func GetDryRunFlag(cmd *cobra.Command) bool {
|
||||
return GetFlagBool(cmd, "dry-run")
|
||||
}
|
||||
|
||||
// GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args
|
||||
func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
|
||||
foundPair := false
|
||||
for _, s := range args {
|
||||
nonResource := (strings.Contains(s, "=") && s[0] != '=') || (strings.HasSuffix(s, "-") && s != "-")
|
||||
switch {
|
||||
case !foundPair && nonResource:
|
||||
foundPair = true
|
||||
fallthrough
|
||||
case foundPair && nonResource:
|
||||
pairArgs = append(pairArgs, s)
|
||||
case !foundPair && !nonResource:
|
||||
resources = append(resources, s)
|
||||
case foundPair && !nonResource:
|
||||
err = fmt.Errorf("all resources must be specified before %s changes: %s", pairType, s)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParsePairs retrieves new and remove pairs (if supportRemove is true) from "KEY=VALUE or KEY-" pair args
|
||||
func ParsePairs(pairArgs []string, pairType string, supportRemove bool) (newPairs map[string]string, removePairs []string, err error) {
|
||||
newPairs = map[string]string{}
|
||||
if supportRemove {
|
||||
removePairs = []string{}
|
||||
}
|
||||
var invalidBuf bytes.Buffer
|
||||
var invalidBufNonEmpty bool
|
||||
for _, pairArg := range pairArgs {
|
||||
if strings.Contains(pairArg, "=") && pairArg[0] != '=' {
|
||||
parts := strings.SplitN(pairArg, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
if invalidBufNonEmpty {
|
||||
invalidBuf.WriteString(", ")
|
||||
}
|
||||
invalidBuf.WriteString(pairArg)
|
||||
invalidBufNonEmpty = true
|
||||
} else {
|
||||
newPairs[parts[0]] = parts[1]
|
||||
}
|
||||
} else if supportRemove && strings.HasSuffix(pairArg, "-") && pairArg != "-" {
|
||||
removePairs = append(removePairs, pairArg[:len(pairArg)-1])
|
||||
} else {
|
||||
if invalidBufNonEmpty {
|
||||
invalidBuf.WriteString(", ")
|
||||
}
|
||||
invalidBuf.WriteString(pairArg)
|
||||
invalidBufNonEmpty = true
|
||||
}
|
||||
}
|
||||
if invalidBufNonEmpty {
|
||||
err = fmt.Errorf("invalid %s format: %s", pairType, invalidBuf.String())
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsSiblingCommandExists receives a pointer to a cobra command and a target string.
|
||||
// Returns true if the target string is found in the list of sibling commands.
|
||||
func IsSiblingCommandExists(cmd *cobra.Command, targetCmdName string) bool {
|
||||
for _, c := range cmd.Parent().Commands() {
|
||||
if c.Name() == targetCmdName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultSubCommandRun prints a command's help string to the specified output if no
|
||||
// arguments (sub-commands) are provided, or a usage error otherwise.
|
||||
func DefaultSubCommandRun(out io.Writer) func(c *cobra.Command, args []string) {
|
||||
return func(c *cobra.Command, args []string) {
|
||||
c.SetOutput(out)
|
||||
RequireNoArguments(c, args)
|
||||
c.Help()
|
||||
CheckErr(ErrExit)
|
||||
}
|
||||
}
|
||||
|
||||
// RequireNoArguments exits with a usage error if extra arguments are provided.
|
||||
func RequireNoArguments(c *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
CheckErr(UsageErrorf(c, "unknown command %q", strings.Join(args, " ")))
|
||||
}
|
||||
}
|
||||
|
||||
// StripComments will transform a YAML file into JSON, thus dropping any comments
|
||||
// in it. Note that if the given file has a syntax error, the transformation will
|
||||
// fail and we will manually drop all comments from the file.
|
||||
func StripComments(file []byte) []byte {
|
||||
stripped := file
|
||||
stripped, err := yaml.ToJSON(stripped)
|
||||
if err != nil {
|
||||
stripped = ManualStrip(file)
|
||||
}
|
||||
return stripped
|
||||
}
|
||||
|
||||
// ManualStrip is used for dropping comments from a YAML file
|
||||
func ManualStrip(file []byte) []byte {
|
||||
stripped := []byte{}
|
||||
lines := bytes.Split(file, []byte("\n"))
|
||||
for i, line := range lines {
|
||||
if bytes.HasPrefix(bytes.TrimSpace(line), []byte("#")) {
|
||||
continue
|
||||
}
|
||||
stripped = append(stripped, line...)
|
||||
if i < len(lines)-1 {
|
||||
stripped = append(stripped, '\n')
|
||||
}
|
||||
}
|
||||
return stripped
|
||||
}
|
||||
|
||||
// ScaleClientFunc provides a ScalesGetter
|
||||
type ScaleClientFunc func(genericclioptions.RESTClientGetter) (scale.ScalesGetter, error)
|
||||
|
||||
// ScaleClientFn gives a way to easily override the function for unit testing if needed.
|
||||
var ScaleClientFn ScaleClientFunc = scaleClient
|
||||
|
||||
// scaleClient gives you back scale getter
|
||||
func scaleClient(restClientGetter genericclioptions.RESTClientGetter) (scale.ScalesGetter, error) {
|
||||
discoveryClient, err := restClientGetter.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientConfig, err := restClientGetter.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setKubernetesDefaults(clientConfig)
|
||||
restClient, err := rest.RESTClientFor(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolver := scale.NewDiscoveryScaleKindResolver(discoveryClient)
|
||||
mapper, err := restClientGetter.ToRESTMapper()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return scale.New(restClient, mapper, dynamic.LegacyAPIPathResolverFunc, resolver), nil
|
||||
}
|
||||
|
||||
func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) {
|
||||
fmt.Fprintf(cmdErr, "WARNING: New generator %q specified, "+
|
||||
"but it isn't available. "+
|
||||
"Falling back to %q.\n",
|
||||
newGeneratorName,
|
||||
oldGeneratorName,
|
||||
)
|
||||
}
|
||||
129
vendor/k8s.io/kubectl/pkg/cmd/util/kubectl_match_version.go
generated
vendored
Normal file
129
vendor/k8s.io/kubectl/pkg/cmd/util/kubectl_match_version.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2018 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 util
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/component-base/version"
|
||||
)
|
||||
|
||||
const (
|
||||
flagMatchBinaryVersion = "match-server-version"
|
||||
)
|
||||
|
||||
// MatchVersionFlags is for setting the "match server version" function.
|
||||
type MatchVersionFlags struct {
|
||||
Delegate genericclioptions.RESTClientGetter
|
||||
|
||||
RequireMatchedServerVersion bool
|
||||
checkServerVersion sync.Once
|
||||
matchesServerVersionErr error
|
||||
}
|
||||
|
||||
var _ genericclioptions.RESTClientGetter = &MatchVersionFlags{}
|
||||
|
||||
func (f *MatchVersionFlags) checkMatchingServerVersion() error {
|
||||
f.checkServerVersion.Do(func() {
|
||||
if !f.RequireMatchedServerVersion {
|
||||
return
|
||||
}
|
||||
discoveryClient, err := f.Delegate.ToDiscoveryClient()
|
||||
if err != nil {
|
||||
f.matchesServerVersionErr = err
|
||||
return
|
||||
}
|
||||
f.matchesServerVersionErr = discovery.MatchesServerVersion(version.Get(), discoveryClient)
|
||||
})
|
||||
|
||||
return f.matchesServerVersionErr
|
||||
}
|
||||
|
||||
// ToRESTConfig implements RESTClientGetter.
|
||||
// Returns a REST client configuration based on a provided path
|
||||
// to a .kubeconfig file, loading rules, and config flag overrides.
|
||||
// Expects the AddFlags method to have been called.
|
||||
func (f *MatchVersionFlags) ToRESTConfig() (*rest.Config, error) {
|
||||
if err := f.checkMatchingServerVersion(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientConfig, err := f.Delegate.ToRESTConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO we should not have to do this. It smacks of something going wrong.
|
||||
setKubernetesDefaults(clientConfig)
|
||||
return clientConfig, nil
|
||||
}
|
||||
|
||||
func (f *MatchVersionFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
return f.Delegate.ToRawKubeConfigLoader()
|
||||
}
|
||||
|
||||
func (f *MatchVersionFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
if err := f.checkMatchingServerVersion(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Delegate.ToDiscoveryClient()
|
||||
}
|
||||
|
||||
// ToRESTMapper returns a mapper.
|
||||
func (f *MatchVersionFlags) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
if err := f.checkMatchingServerVersion(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Delegate.ToRESTMapper()
|
||||
}
|
||||
|
||||
func (f *MatchVersionFlags) AddFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&f.RequireMatchedServerVersion, flagMatchBinaryVersion, f.RequireMatchedServerVersion, "Require server version to match client version")
|
||||
}
|
||||
|
||||
func NewMatchVersionFlags(delegate genericclioptions.RESTClientGetter) *MatchVersionFlags {
|
||||
return &MatchVersionFlags{
|
||||
Delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
// setKubernetesDefaults sets default values on the provided client config for accessing the
|
||||
// Kubernetes API or returns an error if any of the defaults are impossible or invalid.
|
||||
// TODO this isn't what we want. Each clientset should be setting defaults as it sees fit.
|
||||
func setKubernetesDefaults(config *rest.Config) error {
|
||||
// TODO remove this hack. This is allowing the GetOptions to be serialized.
|
||||
config.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"}
|
||||
|
||||
if config.APIPath == "" {
|
||||
config.APIPath = "/api"
|
||||
}
|
||||
if config.NegotiatedSerializer == nil {
|
||||
// This codec factory ensures the resources are not converted. Therefore, resources
|
||||
// will not be round-tripped through internal versions. Defaulting does not happen
|
||||
// on the client.
|
||||
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
|
||||
}
|
||||
return rest.SetKubernetesDefaults(config)
|
||||
}
|
||||
29
vendor/k8s.io/kubectl/pkg/cmd/util/printing.go
generated
vendored
Normal file
29
vendor/k8s.io/kubectl/pkg/cmd/util/printing.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2014 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
|
||||
// SuggestAPIResources returns a suggestion to use the "api-resources" command
|
||||
// to retrieve a supported list of resources
|
||||
func SuggestAPIResources(parent string) string {
|
||||
return templates.LongDesc(fmt.Sprintf("Use \"%s api-resources\" for a complete list of supported resources.", parent))
|
||||
}
|
||||
22850
vendor/k8s.io/kubectl/pkg/generated/bindata.go
generated
vendored
Normal file
22850
vendor/k8s.io/kubectl/pkg/generated/bindata.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
94
vendor/k8s.io/kubectl/pkg/rawhttp/raw.go
generated
vendored
Normal file
94
vendor/k8s.io/kubectl/pkg/rawhttp/raw.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 rawhttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// RawPost uses the REST client to POST content
|
||||
func RawPost(restClient *rest.RESTClient, streams genericclioptions.IOStreams, url, filename string) error {
|
||||
return raw(restClient, streams, url, filename, "POST")
|
||||
}
|
||||
|
||||
// RawPut uses the REST client to PUT content
|
||||
func RawPut(restClient *rest.RESTClient, streams genericclioptions.IOStreams, url, filename string) error {
|
||||
return raw(restClient, streams, url, filename, "PUT")
|
||||
}
|
||||
|
||||
// RawGet uses the REST client to GET content
|
||||
func RawGet(restClient *rest.RESTClient, streams genericclioptions.IOStreams, url string) error {
|
||||
return raw(restClient, streams, url, "", "GET")
|
||||
}
|
||||
|
||||
// RawDelete uses the REST client to DELETE content
|
||||
func RawDelete(restClient *rest.RESTClient, streams genericclioptions.IOStreams, url, filename string) error {
|
||||
return raw(restClient, streams, url, filename, "DELETE")
|
||||
}
|
||||
|
||||
// raw makes a simple HTTP request to the provided path on the server using the default credentials.
|
||||
func raw(restClient *rest.RESTClient, streams genericclioptions.IOStreams, url, filename, requestType string) error {
|
||||
var data io.ReadCloser
|
||||
switch {
|
||||
case len(filename) == 0:
|
||||
data = ioutil.NopCloser(bytes.NewBuffer([]byte{}))
|
||||
|
||||
case filename == "-":
|
||||
data = ioutil.NopCloser(streams.In)
|
||||
|
||||
default:
|
||||
var err error
|
||||
data, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var request *rest.Request
|
||||
switch requestType {
|
||||
case "GET":
|
||||
request = restClient.Get().RequestURI(url)
|
||||
case "PUT":
|
||||
request = restClient.Put().RequestURI(url).Body(data)
|
||||
case "POST":
|
||||
request = restClient.Post().RequestURI(url).Body(data)
|
||||
case "DELETE":
|
||||
request = restClient.Delete().RequestURI(url).Body(data)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown requestType: %q", requestType)
|
||||
}
|
||||
|
||||
stream, err := request.Stream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
_, err = io.Copy(streams.Out, stream)
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
83
vendor/k8s.io/kubectl/pkg/scheme/install.go
generated
vendored
Normal file
83
vendor/k8s.io/kubectl/pkg/scheme/install.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2017 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 scheme
|
||||
|
||||
import (
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
authenticationv1beta1 "k8s.io/api/authentication/v1beta1"
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
imagepolicyv1alpha1 "k8s.io/api/imagepolicy/v1alpha1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
policyv1beta1 "k8s.io/api/policy/v1beta1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
rbacv1alpha1 "k8s.io/api/rbac/v1alpha1"
|
||||
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
|
||||
schedulingv1alpha1 "k8s.io/api/scheduling/v1alpha1"
|
||||
settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
storagev1beta1 "k8s.io/api/storage/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
// Register all groups in the kubectl's registry, but no componentconfig group since it's not in k8s.io/api
|
||||
// The code in this file mostly duplicate the install under k8s.io/kubernetes/pkg/api and k8s.io/kubernetes/pkg/apis,
|
||||
// but does NOT register the internal types.
|
||||
func init() {
|
||||
// Register external types for Scheme
|
||||
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
utilruntime.Must(metav1beta1.AddMetaToScheme(Scheme))
|
||||
utilruntime.Must(metav1.AddMetaToScheme(Scheme))
|
||||
utilruntime.Must(scheme.AddToScheme(Scheme))
|
||||
|
||||
utilruntime.Must(Scheme.SetVersionPriority(corev1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(admissionv1beta1.SchemeGroupVersion, admissionv1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(admissionregistrationv1beta1.SchemeGroupVersion, admissionregistrationv1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion, appsv1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(authenticationv1.SchemeGroupVersion, authenticationv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(authorizationv1.SchemeGroupVersion, authorizationv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(autoscalingv1.SchemeGroupVersion, autoscalingv2beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(batchv1.SchemeGroupVersion, batchv1beta1.SchemeGroupVersion, batchv2alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(certificatesv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(extensionsv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(imagepolicyv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(networkingv1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(policyv1beta1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(rbacv1.SchemeGroupVersion, rbacv1beta1.SchemeGroupVersion, rbacv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(schedulingv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(settingsv1alpha1.SchemeGroupVersion))
|
||||
utilruntime.Must(Scheme.SetVersionPriority(storagev1.SchemeGroupVersion, storagev1beta1.SchemeGroupVersion))
|
||||
}
|
||||
39
vendor/k8s.io/kubectl/pkg/scheme/scheme.go
generated
vendored
Normal file
39
vendor/k8s.io/kubectl/pkg/scheme/scheme.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2017 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 scheme
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
// All kubectl code should eventually switch to use this Registry and Scheme instead of the global ones.
|
||||
|
||||
// Scheme is the default instance of runtime.Scheme to which types in the Kubernetes API are already registered.
|
||||
var Scheme = runtime.NewScheme()
|
||||
|
||||
// Codecs provides access to encoding and decoding for the scheme
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
|
||||
// ParameterCodec handles versioning of objects that are converted to query parameters.
|
||||
var ParameterCodec = runtime.NewParameterCodec(Scheme)
|
||||
|
||||
// DefaultJSONEncoder returns a default encoder for our scheme
|
||||
func DefaultJSONEncoder() runtime.Encoder {
|
||||
return unstructured.NewJSONFallbackEncoder(Codecs.LegacyCodec(Scheme.PrioritizedVersionsAllGroups()...))
|
||||
}
|
||||
147
vendor/k8s.io/kubectl/pkg/util/i18n/i18n.go
generated
vendored
Normal file
147
vendor/k8s.io/kubectl/pkg/util/i18n/i18n.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright 2016 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 i18n
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubectl/pkg/generated"
|
||||
|
||||
"github.com/chai2010/gettext-go/gettext"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
var knownTranslations = map[string][]string{
|
||||
"kubectl": {
|
||||
"default",
|
||||
"en_US",
|
||||
"fr_FR",
|
||||
"zh_CN",
|
||||
"ja_JP",
|
||||
"zh_TW",
|
||||
"it_IT",
|
||||
"de_DE",
|
||||
"ko_KR",
|
||||
},
|
||||
// only used for unit tests.
|
||||
"test": {
|
||||
"default",
|
||||
"en_US",
|
||||
},
|
||||
}
|
||||
|
||||
func loadSystemLanguage() string {
|
||||
// Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG
|
||||
// Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
|
||||
langStr := os.Getenv("LC_ALL")
|
||||
if langStr == "" {
|
||||
langStr = os.Getenv("LC_MESSAGES")
|
||||
}
|
||||
if langStr == "" {
|
||||
langStr = os.Getenv("LANG")
|
||||
}
|
||||
|
||||
if langStr == "" {
|
||||
klog.V(3).Infof("Couldn't find the LC_ALL, LC_MESSAGES or LANG environment variables, defaulting to en_US")
|
||||
return "default"
|
||||
}
|
||||
pieces := strings.Split(langStr, ".")
|
||||
if len(pieces) != 2 {
|
||||
klog.V(3).Infof("Unexpected system language (%s), defaulting to en_US", langStr)
|
||||
return "default"
|
||||
}
|
||||
return pieces[0]
|
||||
}
|
||||
|
||||
func findLanguage(root string, getLanguageFn func() string) string {
|
||||
langStr := getLanguageFn()
|
||||
|
||||
translations := knownTranslations[root]
|
||||
for ix := range translations {
|
||||
if translations[ix] == langStr {
|
||||
return langStr
|
||||
}
|
||||
}
|
||||
klog.V(3).Infof("Couldn't find translations for %s, using default", langStr)
|
||||
return "default"
|
||||
}
|
||||
|
||||
// LoadTranslations loads translation files. getLanguageFn should return a language
|
||||
// string (e.g. 'en-US'). If getLanguageFn is nil, then the loadSystemLanguage function
|
||||
// is used, which uses the 'LANG' environment variable.
|
||||
func LoadTranslations(root string, getLanguageFn func() string) error {
|
||||
if getLanguageFn == nil {
|
||||
getLanguageFn = loadSystemLanguage
|
||||
}
|
||||
|
||||
langStr := findLanguage(root, getLanguageFn)
|
||||
translationFiles := []string{
|
||||
fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.po", root, langStr),
|
||||
fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.mo", root, langStr),
|
||||
}
|
||||
|
||||
klog.V(3).Infof("Setting language to %s", langStr)
|
||||
// TODO: list the directory and load all files.
|
||||
buf := new(bytes.Buffer)
|
||||
w := zip.NewWriter(buf)
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
for _, file := range translationFiles {
|
||||
filename := "translations/" + file
|
||||
f, err := w.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := generated.Asset(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write(data); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
gettext.BindTextdomain("k8s", root+".zip", buf.Bytes())
|
||||
gettext.Textdomain("k8s")
|
||||
gettext.SetLocale(langStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// T translates a string, possibly substituting arguments into it along
|
||||
// the way. If len(args) is > 0, args1 is assumed to be the plural value
|
||||
// and plural translation is used.
|
||||
func T(defaultValue string, args ...int) string {
|
||||
if len(args) == 0 {
|
||||
return gettext.PGettext("", defaultValue)
|
||||
}
|
||||
return fmt.Sprintf(gettext.PNGettext("", defaultValue, defaultValue+".plural", args[0]),
|
||||
args[0])
|
||||
}
|
||||
|
||||
// Errorf produces an error with a translated error string.
|
||||
// Substitution is performed via the `T` function above, following
|
||||
// the same rules.
|
||||
func Errorf(defaultValue string, args ...int) error {
|
||||
return errors.New(T(defaultValue, args...))
|
||||
}
|
||||
104
vendor/k8s.io/kubectl/pkg/util/interrupt/interrupt.go
generated
vendored
Normal file
104
vendor/k8s.io/kubectl/pkg/util/interrupt/interrupt.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright 2016 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 interrupt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// terminationSignals are signals that cause the program to exit in the
|
||||
// supported platforms (linux, darwin, windows).
|
||||
var terminationSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}
|
||||
|
||||
// Handler guarantees execution of notifications after a critical section (the function passed
|
||||
// to a Run method), even in the presence of process termination. It guarantees exactly once
|
||||
// invocation of the provided notify functions.
|
||||
type Handler struct {
|
||||
notify []func()
|
||||
final func(os.Signal)
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Chain creates a new handler that invokes all notify functions when the critical section exits
|
||||
// and then invokes the optional handler's notifications. This allows critical sections to be
|
||||
// nested without losing exactly once invocations. Notify functions can invoke any cleanup needed
|
||||
// but should not exit (which is the responsibility of the parent handler).
|
||||
func Chain(handler *Handler, notify ...func()) *Handler {
|
||||
if handler == nil {
|
||||
return New(nil, notify...)
|
||||
}
|
||||
return New(handler.Signal, append(notify, handler.Close)...)
|
||||
}
|
||||
|
||||
// New creates a new handler that guarantees all notify functions are run after the critical
|
||||
// section exits (or is interrupted by the OS), then invokes the final handler. If no final
|
||||
// handler is specified, the default final is `os.Exit(1)`. A handler can only be used for
|
||||
// one critical section.
|
||||
func New(final func(os.Signal), notify ...func()) *Handler {
|
||||
return &Handler{
|
||||
final: final,
|
||||
notify: notify,
|
||||
}
|
||||
}
|
||||
|
||||
// Close executes all the notification handlers if they have not yet been executed.
|
||||
func (h *Handler) Close() {
|
||||
h.once.Do(func() {
|
||||
for _, fn := range h.notify {
|
||||
fn()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Signal is called when an os.Signal is received, and guarantees that all notifications
|
||||
// are executed, then the final handler is executed. This function should only be called once
|
||||
// per Handler instance.
|
||||
func (h *Handler) Signal(s os.Signal) {
|
||||
h.once.Do(func() {
|
||||
for _, fn := range h.notify {
|
||||
fn()
|
||||
}
|
||||
if h.final == nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
h.final(s)
|
||||
})
|
||||
}
|
||||
|
||||
// Run ensures that any notifications are invoked after the provided fn exits (even if the
|
||||
// process is interrupted by an OS termination signal). Notifications are only invoked once
|
||||
// per Handler instance, so calling Run more than once will not behave as the user expects.
|
||||
func (h *Handler) Run(fn func() error) error {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, terminationSignals...)
|
||||
defer func() {
|
||||
signal.Stop(ch)
|
||||
close(ch)
|
||||
}()
|
||||
go func() {
|
||||
sig, ok := <-ch
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
h.Signal(sig)
|
||||
}()
|
||||
defer h.Close()
|
||||
return fn()
|
||||
}
|
||||
6
vendor/k8s.io/kubectl/pkg/util/openapi/OWNERS
generated
vendored
Normal file
6
vendor/k8s.io/kubectl/pkg/util/openapi/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- apelisse
|
||||
reviewers:
|
||||
- apelisse
|
||||
21
vendor/k8s.io/kubectl/pkg/util/openapi/doc.go
generated
vendored
Normal file
21
vendor/k8s.io/kubectl/pkg/util/openapi/doc.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2017 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 openapi is a collection of libraries for fetching the openapi spec
|
||||
// from a Kubernetes server and then indexing the type definitions.
|
||||
// The openapi spec contains the object model definitions and extensions metadata
|
||||
// such as the patchStrategy and patchMergeKey for creating patches.
|
||||
package openapi // k8s.io/kubectl/pkg/util/openapi
|
||||
65
vendor/k8s.io/kubectl/pkg/util/openapi/dryrun.go
generated
vendored
Normal file
65
vendor/k8s.io/kubectl/pkg/util/openapi/dryrun.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2017 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 openapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func hasGVKExtension(extensions []*openapi_v2.NamedAny, gvk schema.GroupVersionKind) bool {
|
||||
for _, extension := range extensions {
|
||||
if extension.GetValue().GetYaml() == "" ||
|
||||
extension.GetName() != "x-kubernetes-group-version-kind" {
|
||||
continue
|
||||
}
|
||||
var value map[string]string
|
||||
err := yaml.Unmarshal([]byte(extension.GetValue().GetYaml()), &value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if value["group"] == gvk.Group && value["kind"] == gvk.Kind && value["version"] == gvk.Version {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SupportsDryRun is a method that let's us look in the OpenAPI if the
|
||||
// specific group-version-kind supports the dryRun query parameter for
|
||||
// the PATCH end-point.
|
||||
func SupportsDryRun(doc *openapi_v2.Document, gvk schema.GroupVersionKind) (bool, error) {
|
||||
for _, path := range doc.GetPaths().GetPath() {
|
||||
// Is this describing the gvk we're looking for?
|
||||
if !hasGVKExtension(path.GetValue().GetPatch().GetVendorExtension(), gvk) {
|
||||
continue
|
||||
}
|
||||
for _, param := range path.GetValue().GetPatch().GetParameters() {
|
||||
if param.GetParameter().GetNonBodyParameter().GetQueryParameterSubSchema().GetName() == "dryRun" {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, errors.New("couldn't find GVK in openapi")
|
||||
}
|
||||
27
vendor/k8s.io/kubectl/pkg/util/openapi/extensions.go
generated
vendored
Normal file
27
vendor/k8s.io/kubectl/pkg/util/openapi/extensions.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2017 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 openapi
|
||||
|
||||
import "github.com/go-openapi/spec"
|
||||
|
||||
// PrintColumnsKey is the key that defines which columns should be printed
|
||||
const PrintColumnsKey = "x-kubernetes-print-columns"
|
||||
|
||||
// GetPrintColumns looks for the open API extension for the display columns.
|
||||
func GetPrintColumns(extensions spec.Extensions) (string, bool) {
|
||||
return extensions.GetString(PrintColumnsKey)
|
||||
}
|
||||
128
vendor/k8s.io/kubectl/pkg/util/openapi/openapi.go
generated
vendored
Normal file
128
vendor/k8s.io/kubectl/pkg/util/openapi/openapi.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Copyright 2017 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 openapi
|
||||
|
||||
import (
|
||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
)
|
||||
|
||||
// Resources interface describe a resources provider, that can give you
|
||||
// resource based on group-version-kind.
|
||||
type Resources interface {
|
||||
LookupResource(gvk schema.GroupVersionKind) proto.Schema
|
||||
}
|
||||
|
||||
// groupVersionKindExtensionKey is the key used to lookup the
|
||||
// GroupVersionKind value for an object definition from the
|
||||
// definition's "extensions" map.
|
||||
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||
|
||||
// document is an implementation of `Resources`. It looks for
|
||||
// resources in an openapi Schema.
|
||||
type document struct {
|
||||
// Maps gvk to model name
|
||||
resources map[schema.GroupVersionKind]string
|
||||
models proto.Models
|
||||
}
|
||||
|
||||
var _ Resources = &document{}
|
||||
|
||||
// NewOpenAPIData creates a new `Resources` out of the openapi document
|
||||
func NewOpenAPIData(doc *openapi_v2.Document) (Resources, error) {
|
||||
models, err := proto.NewOpenAPIData(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources := map[schema.GroupVersionKind]string{}
|
||||
for _, modelName := range models.ListModels() {
|
||||
model := models.LookupModel(modelName)
|
||||
if model == nil {
|
||||
panic("ListModels returns a model that can't be looked-up.")
|
||||
}
|
||||
gvkList := parseGroupVersionKind(model)
|
||||
for _, gvk := range gvkList {
|
||||
if len(gvk.Kind) > 0 {
|
||||
resources[gvk] = modelName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &document{
|
||||
resources: resources,
|
||||
models: models,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *document) LookupResource(gvk schema.GroupVersionKind) proto.Schema {
|
||||
modelName, found := d.resources[gvk]
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
return d.models.LookupModel(modelName)
|
||||
}
|
||||
|
||||
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
|
||||
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
|
||||
extensions := s.GetExtensions()
|
||||
|
||||
gvkListResult := []schema.GroupVersionKind{}
|
||||
|
||||
// Get the extensions
|
||||
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
// gvk extension must be a list of at least 1 element.
|
||||
gvkList, ok := gvkExtension.([]interface{})
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
for _, gvk := range gvkList {
|
||||
// gvk extension list must be a map with group, version, and
|
||||
// kind fields
|
||||
gvkMap, ok := gvk.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
group, ok := gvkMap["group"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version, ok := gvkMap["version"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
kind, ok := gvkMap["kind"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
return gvkListResult
|
||||
}
|
||||
65
vendor/k8s.io/kubectl/pkg/util/openapi/openapi_getter.go
generated
vendored
Normal file
65
vendor/k8s.io/kubectl/pkg/util/openapi/openapi_getter.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2017 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 openapi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
// synchronizedOpenAPIGetter fetches the openapi schema once and then caches it in memory
|
||||
type synchronizedOpenAPIGetter struct {
|
||||
// Cached results
|
||||
sync.Once
|
||||
openAPISchema Resources
|
||||
err error
|
||||
|
||||
openAPIClient discovery.OpenAPISchemaInterface
|
||||
}
|
||||
|
||||
var _ Getter = &synchronizedOpenAPIGetter{}
|
||||
|
||||
// Getter is an interface for fetching openapi specs and parsing them into an Resources struct
|
||||
type Getter interface {
|
||||
// OpenAPIData returns the parsed OpenAPIData
|
||||
Get() (Resources, error)
|
||||
}
|
||||
|
||||
// NewOpenAPIGetter returns an object to return OpenAPIDatas which reads
|
||||
// from a server, and then stores in memory for subsequent invocations
|
||||
func NewOpenAPIGetter(openAPIClient discovery.OpenAPISchemaInterface) Getter {
|
||||
return &synchronizedOpenAPIGetter{
|
||||
openAPIClient: openAPIClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Resources implements Getter
|
||||
func (g *synchronizedOpenAPIGetter) Get() (Resources, error) {
|
||||
g.Do(func() {
|
||||
s, err := g.openAPIClient.OpenAPISchema()
|
||||
if err != nil {
|
||||
g.err = err
|
||||
return
|
||||
}
|
||||
|
||||
g.openAPISchema, g.err = NewOpenAPIData(s)
|
||||
})
|
||||
|
||||
// Return the save result
|
||||
return g.openAPISchema, g.err
|
||||
}
|
||||
140
vendor/k8s.io/kubectl/pkg/util/openapi/validation/validation.go
generated
vendored
Normal file
140
vendor/k8s.io/kubectl/pkg/util/openapi/validation/validation.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright 2017 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 validation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/kube-openapi/pkg/util/proto/validation"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
)
|
||||
|
||||
// SchemaValidation validates the object against an OpenAPI schema.
|
||||
type SchemaValidation struct {
|
||||
resources openapi.Resources
|
||||
}
|
||||
|
||||
// NewSchemaValidation creates a new SchemaValidation that can be used
|
||||
// to validate objects.
|
||||
func NewSchemaValidation(resources openapi.Resources) *SchemaValidation {
|
||||
return &SchemaValidation{
|
||||
resources: resources,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateBytes will validates the object against using the Resources
|
||||
// object.
|
||||
func (v *SchemaValidation) ValidateBytes(data []byte) error {
|
||||
obj, err := parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gvk, errs := getObjectKind(obj)
|
||||
if errs != nil {
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
if (gvk == schema.GroupVersionKind{Version: "v1", Kind: "List"}) {
|
||||
return utilerrors.NewAggregate(v.validateList(obj))
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(v.validateResource(obj, gvk))
|
||||
}
|
||||
|
||||
func (v *SchemaValidation) validateList(object interface{}) []error {
|
||||
fields, ok := object.(map[string]interface{})
|
||||
if !ok || fields == nil {
|
||||
return []error{errors.New("invalid object to validate")}
|
||||
}
|
||||
|
||||
allErrors := []error{}
|
||||
if _, ok := fields["items"].([]interface{}); !ok {
|
||||
return []error{errors.New("invalid object to validate")}
|
||||
}
|
||||
for _, item := range fields["items"].([]interface{}) {
|
||||
if gvk, errs := getObjectKind(item); errs != nil {
|
||||
allErrors = append(allErrors, errs...)
|
||||
} else {
|
||||
allErrors = append(allErrors, v.validateResource(item, gvk)...)
|
||||
}
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVersionKind) []error {
|
||||
resource := v.resources.LookupResource(gvk)
|
||||
if resource == nil {
|
||||
// resource is not present, let's just skip validation.
|
||||
return nil
|
||||
}
|
||||
|
||||
return validation.ValidateModel(obj, resource, gvk.Kind)
|
||||
}
|
||||
|
||||
func parse(data []byte) (interface{}, error) {
|
||||
var obj interface{}
|
||||
out, err := yaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(out, &obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) {
|
||||
var listErrors []error
|
||||
fields, ok := object.(map[string]interface{})
|
||||
if !ok || fields == nil {
|
||||
listErrors = append(listErrors, errors.New("invalid object to validate"))
|
||||
return schema.GroupVersionKind{}, listErrors
|
||||
}
|
||||
|
||||
var group string
|
||||
var version string
|
||||
apiVersion := fields["apiVersion"]
|
||||
if apiVersion == nil {
|
||||
listErrors = append(listErrors, errors.New("apiVersion not set"))
|
||||
} else if _, ok := apiVersion.(string); !ok {
|
||||
listErrors = append(listErrors, errors.New("apiVersion isn't string type"))
|
||||
} else {
|
||||
gv, err := schema.ParseGroupVersion(apiVersion.(string))
|
||||
if err != nil {
|
||||
listErrors = append(listErrors, err)
|
||||
} else {
|
||||
group = gv.Group
|
||||
version = gv.Version
|
||||
}
|
||||
}
|
||||
kind := fields["kind"]
|
||||
if kind == nil {
|
||||
listErrors = append(listErrors, errors.New("kind not set"))
|
||||
} else if _, ok := kind.(string); !ok {
|
||||
listErrors = append(listErrors, errors.New("kind isn't string type"))
|
||||
}
|
||||
if listErrors != nil {
|
||||
return schema.GroupVersionKind{}, listErrors
|
||||
}
|
||||
|
||||
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind.(string)}, nil
|
||||
}
|
||||
59
vendor/k8s.io/kubectl/pkg/util/templates/command_groups.go
generated
vendored
Normal file
59
vendor/k8s.io/kubectl/pkg/util/templates/command_groups.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2016 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 templates
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type CommandGroup struct {
|
||||
Message string
|
||||
Commands []*cobra.Command
|
||||
}
|
||||
|
||||
type CommandGroups []CommandGroup
|
||||
|
||||
func (g CommandGroups) Add(c *cobra.Command) {
|
||||
for _, group := range g {
|
||||
c.AddCommand(group.Commands...)
|
||||
}
|
||||
}
|
||||
|
||||
func (g CommandGroups) Has(c *cobra.Command) bool {
|
||||
for _, group := range g {
|
||||
for _, command := range group.Commands {
|
||||
if command == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups {
|
||||
group := CommandGroup{Message: message}
|
||||
for _, c := range cmds {
|
||||
// Don't show commands that have no short description
|
||||
if !g.Has(c) && len(c.Short) != 0 {
|
||||
group.Commands = append(group.Commands, c)
|
||||
}
|
||||
}
|
||||
if len(group.Commands) == 0 {
|
||||
return g
|
||||
}
|
||||
return append(g, group)
|
||||
}
|
||||
147
vendor/k8s.io/kubectl/pkg/util/templates/markdown.go
generated
vendored
Normal file
147
vendor/k8s.io/kubectl/pkg/util/templates/markdown.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright 2016 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 templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
const linebreak = "\n"
|
||||
|
||||
// ASCIIRenderer implements blackfriday.Renderer
|
||||
var _ blackfriday.Renderer = &ASCIIRenderer{}
|
||||
|
||||
// ASCIIRenderer is a blackfriday.Renderer intended for rendering markdown
|
||||
// documents as plain text, well suited for human reading on terminals.
|
||||
type ASCIIRenderer struct {
|
||||
Indentation string
|
||||
|
||||
listItemCount uint
|
||||
listLevel uint
|
||||
}
|
||||
|
||||
// NormalText gets a text chunk *after* the markdown syntax was already
|
||||
// processed and does a final cleanup on things we don't expect here, like
|
||||
// removing linebreaks on things that are not a paragraph break (auto unwrap).
|
||||
func (r *ASCIIRenderer) NormalText(out *bytes.Buffer, text []byte) {
|
||||
raw := string(text)
|
||||
lines := strings.Split(raw, linebreak)
|
||||
for _, line := range lines {
|
||||
trimmed := strings.Trim(line, " \n\t")
|
||||
if len(trimmed) > 0 && trimmed[0] != '_' {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
out.WriteString(trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
// List renders the start and end of a list.
|
||||
func (r *ASCIIRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
r.listLevel++
|
||||
out.WriteString(linebreak)
|
||||
text()
|
||||
r.listLevel--
|
||||
}
|
||||
|
||||
// ListItem renders list items and supports both ordered and unordered lists.
|
||||
func (r *ASCIIRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
if flags&blackfriday.LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||
r.listItemCount = 1
|
||||
} else {
|
||||
r.listItemCount++
|
||||
}
|
||||
indent := strings.Repeat(r.Indentation, int(r.listLevel))
|
||||
var bullet string
|
||||
if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
|
||||
bullet += fmt.Sprintf("%d.", r.listItemCount)
|
||||
} else {
|
||||
bullet += "*"
|
||||
}
|
||||
out.WriteString(indent + bullet + " ")
|
||||
r.fw(out, text)
|
||||
out.WriteString(linebreak)
|
||||
}
|
||||
|
||||
// Paragraph renders the start and end of a paragraph.
|
||||
func (r *ASCIIRenderer) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
out.WriteString(linebreak)
|
||||
text()
|
||||
out.WriteString(linebreak)
|
||||
}
|
||||
|
||||
// BlockCode renders a chunk of text that represents source code.
|
||||
func (r *ASCIIRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
out.WriteString(linebreak)
|
||||
lines := []string{}
|
||||
for _, line := range strings.Split(string(text), linebreak) {
|
||||
indented := r.Indentation + line
|
||||
lines = append(lines, indented)
|
||||
}
|
||||
out.WriteString(strings.Join(lines, linebreak))
|
||||
}
|
||||
|
||||
func (r *ASCIIRenderer) GetFlags() int { return 0 }
|
||||
func (r *ASCIIRenderer) HRule(out *bytes.Buffer) {
|
||||
out.WriteString(linebreak + "----------" + linebreak)
|
||||
}
|
||||
func (r *ASCIIRenderer) LineBreak(out *bytes.Buffer) { out.WriteString(linebreak) }
|
||||
func (r *ASCIIRenderer) TitleBlock(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) { text() }
|
||||
func (r *ASCIIRenderer) BlockHtml(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) BlockQuote(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) TableRow(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) TableCell(out *bytes.Buffer, text []byte, align int) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) Footnotes(out *bytes.Buffer, text func() bool) { text() }
|
||||
func (r *ASCIIRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) { r.fw(out, link) }
|
||||
func (r *ASCIIRenderer) CodeSpan(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) Emphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) RawHtmlTag(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) StrikeThrough(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { r.fw(out, ref) }
|
||||
func (r *ASCIIRenderer) Entity(out *bytes.Buffer, entity []byte) { r.fw(out, entity) }
|
||||
func (r *ASCIIRenderer) Smartypants(out *bytes.Buffer, text []byte) { r.fw(out, text) }
|
||||
func (r *ASCIIRenderer) DocumentHeader(out *bytes.Buffer) {}
|
||||
func (r *ASCIIRenderer) DocumentFooter(out *bytes.Buffer) {}
|
||||
func (r *ASCIIRenderer) TocHeaderWithAnchor(text []byte, level int, anchor string) {}
|
||||
func (r *ASCIIRenderer) TocHeader(text []byte, level int) {}
|
||||
func (r *ASCIIRenderer) TocFinalize() {}
|
||||
|
||||
func (r *ASCIIRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||
r.fw(out, header, body)
|
||||
}
|
||||
|
||||
func (r *ASCIIRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||
r.fw(out, link)
|
||||
}
|
||||
|
||||
func (r *ASCIIRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
r.fw(out, link)
|
||||
}
|
||||
|
||||
func (r *ASCIIRenderer) fw(out *bytes.Buffer, text ...[]byte) {
|
||||
for _, t := range text {
|
||||
out.Write(t)
|
||||
}
|
||||
}
|
||||
97
vendor/k8s.io/kubectl/pkg/util/templates/normalizers.go
generated
vendored
Normal file
97
vendor/k8s.io/kubectl/pkg/util/templates/normalizers.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2016 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 templates
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/russross/blackfriday"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const Indentation = ` `
|
||||
|
||||
// LongDesc normalizes a command's long description to follow the conventions.
|
||||
func LongDesc(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
return normalizer{s}.heredoc().markdown().trim().string
|
||||
}
|
||||
|
||||
// Examples normalizes a command's examples to follow the conventions.
|
||||
func Examples(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
return normalizer{s}.trim().indent().string
|
||||
}
|
||||
|
||||
// Normalize perform all required normalizations on a given command.
|
||||
func Normalize(cmd *cobra.Command) *cobra.Command {
|
||||
if len(cmd.Long) > 0 {
|
||||
cmd.Long = LongDesc(cmd.Long)
|
||||
}
|
||||
if len(cmd.Example) > 0 {
|
||||
cmd.Example = Examples(cmd.Example)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NormalizeAll perform all required normalizations in the entire command tree.
|
||||
func NormalizeAll(cmd *cobra.Command) *cobra.Command {
|
||||
if cmd.HasSubCommands() {
|
||||
for _, subCmd := range cmd.Commands() {
|
||||
NormalizeAll(subCmd)
|
||||
}
|
||||
}
|
||||
Normalize(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type normalizer struct {
|
||||
string
|
||||
}
|
||||
|
||||
func (s normalizer) markdown() normalizer {
|
||||
bytes := []byte(s.string)
|
||||
formatted := blackfriday.Markdown(bytes, &ASCIIRenderer{Indentation: Indentation}, blackfriday.EXTENSION_NO_INTRA_EMPHASIS)
|
||||
s.string = string(formatted)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s normalizer) heredoc() normalizer {
|
||||
s.string = heredoc.Doc(s.string)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s normalizer) trim() normalizer {
|
||||
s.string = strings.TrimSpace(s.string)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s normalizer) indent() normalizer {
|
||||
indentedLines := []string{}
|
||||
for _, line := range strings.Split(s.string, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
indented := Indentation + trimmed
|
||||
indentedLines = append(indentedLines, indented)
|
||||
}
|
||||
s.string = strings.Join(indentedLines, "\n")
|
||||
return s
|
||||
}
|
||||
297
vendor/k8s.io/kubectl/pkg/util/templates/templater.go
generated
vendored
Normal file
297
vendor/k8s.io/kubectl/pkg/util/templates/templater.go
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
Copyright 2016 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 templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type FlagExposer interface {
|
||||
ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer
|
||||
}
|
||||
|
||||
func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGroup) FlagExposer {
|
||||
if cmd == nil {
|
||||
panic("nil root command")
|
||||
}
|
||||
templater := &templater{
|
||||
RootCmd: cmd,
|
||||
UsageTemplate: MainUsageTemplate(),
|
||||
HelpTemplate: MainHelpTemplate(),
|
||||
CommandGroups: groups,
|
||||
Filtered: filters,
|
||||
}
|
||||
cmd.SetFlagErrorFunc(templater.FlagErrorFunc())
|
||||
cmd.SetUsageFunc(templater.UsageFunc())
|
||||
cmd.SetHelpFunc(templater.HelpFunc())
|
||||
return templater
|
||||
}
|
||||
|
||||
func UseOptionsTemplates(cmd *cobra.Command) {
|
||||
templater := &templater{
|
||||
UsageTemplate: OptionsUsageTemplate(),
|
||||
HelpTemplate: OptionsHelpTemplate(),
|
||||
}
|
||||
cmd.SetUsageFunc(templater.UsageFunc())
|
||||
cmd.SetHelpFunc(templater.HelpFunc())
|
||||
}
|
||||
|
||||
type templater struct {
|
||||
UsageTemplate string
|
||||
HelpTemplate string
|
||||
RootCmd *cobra.Command
|
||||
CommandGroups
|
||||
Filtered []string
|
||||
}
|
||||
|
||||
func (templater *templater) FlagErrorFunc(exposedFlags ...string) func(*cobra.Command, error) error {
|
||||
return func(c *cobra.Command, err error) error {
|
||||
c.SilenceUsage = true
|
||||
switch c.CalledAs() {
|
||||
case "options":
|
||||
return fmt.Errorf("%s\nRun '%s' without flags.", err, c.CommandPath())
|
||||
default:
|
||||
return fmt.Errorf("%s\nSee '%s --help' for usage.", err, c.CommandPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (templater *templater) ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer {
|
||||
cmd.SetUsageFunc(templater.UsageFunc(flags...))
|
||||
return templater
|
||||
}
|
||||
|
||||
func (templater *templater) HelpFunc() func(*cobra.Command, []string) {
|
||||
return func(c *cobra.Command, s []string) {
|
||||
t := template.New("help")
|
||||
t.Funcs(templater.templateFuncs())
|
||||
template.Must(t.Parse(templater.HelpTemplate))
|
||||
out := term.NewResponsiveWriter(c.OutOrStdout())
|
||||
err := t.Execute(out, c)
|
||||
if err != nil {
|
||||
c.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (templater *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error {
|
||||
return func(c *cobra.Command) error {
|
||||
t := template.New("usage")
|
||||
t.Funcs(templater.templateFuncs(exposedFlags...))
|
||||
template.Must(t.Parse(templater.UsageTemplate))
|
||||
out := term.NewResponsiveWriter(c.OutOrStderr())
|
||||
return t.Execute(out, c)
|
||||
}
|
||||
}
|
||||
|
||||
func (templater *templater) templateFuncs(exposedFlags ...string) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"trim": strings.TrimSpace,
|
||||
"trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) },
|
||||
"trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) },
|
||||
"gt": cobra.Gt,
|
||||
"eq": cobra.Eq,
|
||||
"rpad": rpad,
|
||||
"appendIfNotPresent": appendIfNotPresent,
|
||||
"flagsNotIntersected": flagsNotIntersected,
|
||||
"visibleFlags": visibleFlags,
|
||||
"flagsUsages": flagsUsages,
|
||||
"cmdGroups": templater.cmdGroups,
|
||||
"cmdGroupsString": templater.cmdGroupsString,
|
||||
"rootCmd": templater.rootCmdName,
|
||||
"isRootCmd": templater.isRootCmd,
|
||||
"optionsCmdFor": templater.optionsCmdFor,
|
||||
"usageLine": templater.usageLine,
|
||||
"exposed": func(c *cobra.Command) *flag.FlagSet {
|
||||
exposed := flag.NewFlagSet("exposed", flag.ContinueOnError)
|
||||
if len(exposedFlags) > 0 {
|
||||
for _, name := range exposedFlags {
|
||||
if flag := c.Flags().Lookup(name); flag != nil {
|
||||
exposed.AddFlag(flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return exposed
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (templater *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) []CommandGroup {
|
||||
if len(templater.CommandGroups) > 0 && c == templater.RootCmd {
|
||||
all = filter(all, templater.Filtered...)
|
||||
return AddAdditionalCommands(templater.CommandGroups, "Other Commands:", all)
|
||||
}
|
||||
all = filter(all, "options")
|
||||
return []CommandGroup{
|
||||
{
|
||||
Message: "Available Commands:",
|
||||
Commands: all,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *templater) cmdGroupsString(c *cobra.Command) string {
|
||||
groups := []string{}
|
||||
for _, cmdGroup := range t.cmdGroups(c, c.Commands()) {
|
||||
cmds := []string{cmdGroup.Message}
|
||||
for _, cmd := range cmdGroup.Commands {
|
||||
if cmd.IsAvailableCommand() {
|
||||
cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short)
|
||||
}
|
||||
}
|
||||
groups = append(groups, strings.Join(cmds, "\n"))
|
||||
}
|
||||
return strings.Join(groups, "\n\n")
|
||||
}
|
||||
|
||||
func (t *templater) rootCmdName(c *cobra.Command) string {
|
||||
return t.rootCmd(c).CommandPath()
|
||||
}
|
||||
|
||||
func (t *templater) isRootCmd(c *cobra.Command) bool {
|
||||
return t.rootCmd(c) == c
|
||||
}
|
||||
|
||||
func (t *templater) parents(c *cobra.Command) []*cobra.Command {
|
||||
parents := []*cobra.Command{c}
|
||||
for current := c; !t.isRootCmd(current) && current.HasParent(); {
|
||||
current = current.Parent()
|
||||
parents = append(parents, current)
|
||||
}
|
||||
return parents
|
||||
}
|
||||
|
||||
func (t *templater) rootCmd(c *cobra.Command) *cobra.Command {
|
||||
if c != nil && !c.HasParent() {
|
||||
return c
|
||||
}
|
||||
if t.RootCmd == nil {
|
||||
panic("nil root cmd")
|
||||
}
|
||||
return t.RootCmd
|
||||
}
|
||||
|
||||
func (t *templater) optionsCmdFor(c *cobra.Command) string {
|
||||
if !c.Runnable() {
|
||||
return ""
|
||||
}
|
||||
rootCmdStructure := t.parents(c)
|
||||
for i := len(rootCmdStructure) - 1; i >= 0; i-- {
|
||||
cmd := rootCmdStructure[i]
|
||||
if _, _, err := cmd.Find([]string{"options"}); err == nil {
|
||||
return cmd.CommandPath() + " options"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *templater) usageLine(c *cobra.Command) string {
|
||||
usage := c.UseLine()
|
||||
suffix := "[options]"
|
||||
if c.HasFlags() && !strings.Contains(usage, suffix) {
|
||||
usage += " " + suffix
|
||||
}
|
||||
return usage
|
||||
}
|
||||
|
||||
func flagsUsages(f *flag.FlagSet) string {
|
||||
x := new(bytes.Buffer)
|
||||
|
||||
f.VisitAll(func(flag *flag.Flag) {
|
||||
if flag.Hidden {
|
||||
return
|
||||
}
|
||||
format := "--%s=%s: %s\n"
|
||||
|
||||
if flag.Value.Type() == "string" {
|
||||
format = "--%s='%s': %s\n"
|
||||
}
|
||||
|
||||
if len(flag.Shorthand) > 0 {
|
||||
format = " -%s, " + format
|
||||
} else {
|
||||
format = " %s " + format
|
||||
}
|
||||
|
||||
fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
|
||||
})
|
||||
|
||||
return x.String()
|
||||
}
|
||||
|
||||
func rpad(s string, padding int) string {
|
||||
template := fmt.Sprintf("%%-%ds", padding)
|
||||
return fmt.Sprintf(template, s)
|
||||
}
|
||||
|
||||
func appendIfNotPresent(s, stringToAppend string) string {
|
||||
if strings.Contains(s, stringToAppend) {
|
||||
return s
|
||||
}
|
||||
return s + " " + stringToAppend
|
||||
}
|
||||
|
||||
func flagsNotIntersected(l *flag.FlagSet, r *flag.FlagSet) *flag.FlagSet {
|
||||
f := flag.NewFlagSet("notIntersected", flag.ContinueOnError)
|
||||
l.VisitAll(func(flag *flag.Flag) {
|
||||
if r.Lookup(flag.Name) == nil {
|
||||
f.AddFlag(flag)
|
||||
}
|
||||
})
|
||||
return f
|
||||
}
|
||||
|
||||
func visibleFlags(l *flag.FlagSet) *flag.FlagSet {
|
||||
hidden := "help"
|
||||
f := flag.NewFlagSet("visible", flag.ContinueOnError)
|
||||
l.VisitAll(func(flag *flag.Flag) {
|
||||
if flag.Name != hidden {
|
||||
f.AddFlag(flag)
|
||||
}
|
||||
})
|
||||
return f
|
||||
}
|
||||
|
||||
func filter(cmds []*cobra.Command, names ...string) []*cobra.Command {
|
||||
out := []*cobra.Command{}
|
||||
for _, c := range cmds {
|
||||
if c.Hidden {
|
||||
continue
|
||||
}
|
||||
skip := false
|
||||
for _, name := range names {
|
||||
if name == c.Name() {
|
||||
skip = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
103
vendor/k8s.io/kubectl/pkg/util/templates/templates.go
generated
vendored
Normal file
103
vendor/k8s.io/kubectl/pkg/util/templates/templates.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2016 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 templates
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
// SectionVars is the help template section that declares variables to be used in the template.
|
||||
SectionVars = `{{$isRootCmd := isRootCmd .}}` +
|
||||
`{{$rootCmd := rootCmd .}}` +
|
||||
`{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` +
|
||||
`{{$explicitlyExposedFlags := exposed .}}` +
|
||||
`{{$optionsCmdFor := optionsCmdFor .}}` +
|
||||
`{{$usageLine := usageLine .}}`
|
||||
|
||||
// SectionAliases is the help template section that displays command aliases.
|
||||
SectionAliases = `{{if gt .Aliases 0}}Aliases:
|
||||
{{.NameAndAliases}}
|
||||
|
||||
{{end}}`
|
||||
|
||||
// SectionExamples is the help template section that displays command examples.
|
||||
SectionExamples = `{{if .HasExample}}Examples:
|
||||
{{trimRight .Example}}
|
||||
|
||||
{{end}}`
|
||||
|
||||
// SectionSubcommands is the help template section that displays the command's subcommands.
|
||||
SectionSubcommands = `{{if .HasAvailableSubCommands}}{{cmdGroupsString .}}
|
||||
|
||||
{{end}}`
|
||||
|
||||
// SectionFlags is the help template section that displays the command's flags.
|
||||
SectionFlags = `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}}Options:
|
||||
{{ if $visibleFlags.HasFlags}}{{trimRight (flagsUsages $visibleFlags)}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{ if $visibleFlags.HasFlags}}
|
||||
{{end}}{{trimRight (flagsUsages $explicitlyExposedFlags)}}{{end}}
|
||||
|
||||
{{end}}`
|
||||
|
||||
// SectionUsage is the help template section that displays the command's usage.
|
||||
SectionUsage = `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}}Usage:
|
||||
{{$usageLine}}
|
||||
|
||||
{{end}}`
|
||||
|
||||
// SectionTipsHelp is the help template section that displays the '--help' hint.
|
||||
SectionTipsHelp = `{{if .HasSubCommands}}Use "{{$rootCmd}} <command> --help" for more information about a given command.
|
||||
{{end}}`
|
||||
|
||||
// SectionTipsGlobalOptions is the help template section that displays the 'options' hint for displaying global flags.
|
||||
SectionTipsGlobalOptions = `{{if $optionsCmdFor}}Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands).
|
||||
{{end}}`
|
||||
)
|
||||
|
||||
// MainHelpTemplate if the template for 'help' used by most commands.
|
||||
func MainHelpTemplate() string {
|
||||
return `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
||||
}
|
||||
|
||||
// MainUsageTemplate if the template for 'usage' used by most commands.
|
||||
func MainUsageTemplate() string {
|
||||
sections := []string{
|
||||
"\n\n",
|
||||
SectionVars,
|
||||
SectionAliases,
|
||||
SectionExamples,
|
||||
SectionSubcommands,
|
||||
SectionFlags,
|
||||
SectionUsage,
|
||||
SectionTipsHelp,
|
||||
SectionTipsGlobalOptions,
|
||||
}
|
||||
return strings.TrimRightFunc(strings.Join(sections, ""), unicode.IsSpace)
|
||||
}
|
||||
|
||||
// OptionsHelpTemplate if the template for 'help' used by the 'options' command.
|
||||
func OptionsHelpTemplate() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// OptionsUsageTemplate if the template for 'usage' used by the 'options' command.
|
||||
func OptionsUsageTemplate() string {
|
||||
return `{{ if .HasInheritedFlags}}The following options can be passed to any command:
|
||||
|
||||
{{flagsUsages .InheritedFlags}}{{end}}`
|
||||
}
|
||||
132
vendor/k8s.io/kubectl/pkg/util/term/resize.go
generated
vendored
Normal file
132
vendor/k8s.io/kubectl/pkg/util/term/resize.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Copyright 2016 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 term
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// GetSize returns the current size of the user's terminal. If it isn't a terminal,
|
||||
// nil is returned.
|
||||
func (t TTY) GetSize() *remotecommand.TerminalSize {
|
||||
outFd, isTerminal := term.GetFdInfo(t.Out)
|
||||
if !isTerminal {
|
||||
return nil
|
||||
}
|
||||
return GetSize(outFd)
|
||||
}
|
||||
|
||||
// GetSize returns the current size of the terminal associated with fd.
|
||||
func GetSize(fd uintptr) *remotecommand.TerminalSize {
|
||||
winsize, err := term.GetWinsize(fd)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &remotecommand.TerminalSize{Width: winsize.Width, Height: winsize.Height}
|
||||
}
|
||||
|
||||
// MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with
|
||||
// initialSizes, or nil if there's no TTY present.
|
||||
func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue {
|
||||
outFd, isTerminal := term.GetFdInfo(t.Out)
|
||||
if !isTerminal {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.sizeQueue = &sizeQueue{
|
||||
t: *t,
|
||||
// make it buffered so we can send the initial terminal sizes without blocking, prior to starting
|
||||
// the streaming below
|
||||
resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)),
|
||||
stopResizing: make(chan struct{}),
|
||||
}
|
||||
|
||||
t.sizeQueue.monitorSize(outFd, initialSizes...)
|
||||
|
||||
return t.sizeQueue
|
||||
}
|
||||
|
||||
// sizeQueue implements remotecommand.TerminalSizeQueue
|
||||
type sizeQueue struct {
|
||||
t TTY
|
||||
// resizeChan receives a Size each time the user's terminal is resized.
|
||||
resizeChan chan remotecommand.TerminalSize
|
||||
stopResizing chan struct{}
|
||||
}
|
||||
|
||||
// make sure sizeQueue implements the resize.TerminalSizeQueue interface
|
||||
var _ remotecommand.TerminalSizeQueue = &sizeQueue{}
|
||||
|
||||
// monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each
|
||||
// new event, it sends the current terminal size to resizeChan.
|
||||
func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) {
|
||||
// send the initial sizes
|
||||
for i := range initialSizes {
|
||||
if initialSizes[i] != nil {
|
||||
s.resizeChan <- *initialSizes[i]
|
||||
}
|
||||
}
|
||||
|
||||
resizeEvents := make(chan remotecommand.TerminalSize, 1)
|
||||
|
||||
monitorResizeEvents(outFd, resizeEvents, s.stopResizing)
|
||||
|
||||
// listen for resize events in the background
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
for {
|
||||
select {
|
||||
case size, ok := <-resizeEvents:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
// try to send the size to resizeChan, but don't block
|
||||
case s.resizeChan <- size:
|
||||
// send successful
|
||||
default:
|
||||
// unable to send / no-op
|
||||
}
|
||||
case <-s.stopResizing:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Next returns the new terminal size after the terminal has been resized. It returns nil when
|
||||
// monitoring has been stopped.
|
||||
func (s *sizeQueue) Next() *remotecommand.TerminalSize {
|
||||
size, ok := <-s.resizeChan
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &size
|
||||
}
|
||||
|
||||
// stop stops the background goroutine that is monitoring for terminal resizes.
|
||||
func (s *sizeQueue) stop() {
|
||||
close(s.stopResizing)
|
||||
}
|
||||
61
vendor/k8s.io/kubectl/pkg/util/term/resizeevents.go
generated
vendored
Normal file
61
vendor/k8s.io/kubectl/pkg/util/term/resizeevents.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2016 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 term
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the
|
||||
// terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send
|
||||
// it to the resizeEvents channel. The goroutine stops when the stop channel is closed.
|
||||
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
winch := make(chan os.Signal, 1)
|
||||
signal.Notify(winch, unix.SIGWINCH)
|
||||
defer signal.Stop(winch)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-winch:
|
||||
size := GetSize(fd)
|
||||
if size == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// try to send size
|
||||
select {
|
||||
case resizeEvents <- *size:
|
||||
// success
|
||||
default:
|
||||
// not sent
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
62
vendor/k8s.io/kubectl/pkg/util/term/resizeevents_windows.go
generated
vendored
Normal file
62
vendor/k8s.io/kubectl/pkg/util/term/resizeevents_windows.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2016 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 term
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// monitorResizeEvents spawns a goroutine that periodically gets the terminal size and tries to send
|
||||
// it to the resizeEvents channel if the size has changed. The goroutine stops when the stop channel
|
||||
// is closed.
|
||||
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
size := GetSize(fd)
|
||||
if size == nil {
|
||||
return
|
||||
}
|
||||
lastSize := *size
|
||||
|
||||
for {
|
||||
// see if we need to stop running
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
size := GetSize(fd)
|
||||
if size == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if size.Height != lastSize.Height || size.Width != lastSize.Width {
|
||||
lastSize.Height = size.Height
|
||||
lastSize.Width = size.Width
|
||||
resizeEvents <- *size
|
||||
}
|
||||
|
||||
// sleep to avoid hot looping
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
}
|
||||
110
vendor/k8s.io/kubectl/pkg/util/term/term.go
generated
vendored
Normal file
110
vendor/k8s.io/kubectl/pkg/util/term/term.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Copyright 2016 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 term
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
|
||||
"k8s.io/kubectl/pkg/util/interrupt"
|
||||
)
|
||||
|
||||
// SafeFunc is a function to be invoked by TTY.
|
||||
type SafeFunc func() error
|
||||
|
||||
// TTY helps invoke a function and preserve the state of the terminal, even if the process is
|
||||
// terminated during execution. It also provides support for terminal resizing for remote command
|
||||
// execution/attachment.
|
||||
type TTY struct {
|
||||
// In is a reader representing stdin. It is a required field.
|
||||
In io.Reader
|
||||
// Out is a writer representing stdout. It must be set to support terminal resizing. It is an
|
||||
// optional field.
|
||||
Out io.Writer
|
||||
// Raw is true if the terminal should be set raw.
|
||||
Raw bool
|
||||
// TryDev indicates the TTY should try to open /dev/tty if the provided input
|
||||
// is not a file descriptor.
|
||||
TryDev bool
|
||||
// Parent is an optional interrupt handler provided to this function - if provided
|
||||
// it will be invoked after the terminal state is restored. If it is not provided,
|
||||
// a signal received during the TTY will result in os.Exit(0) being invoked.
|
||||
Parent *interrupt.Handler
|
||||
|
||||
// sizeQueue is set after a call to MonitorSize() and is used to monitor SIGWINCH signals when the
|
||||
// user's terminal resizes.
|
||||
sizeQueue *sizeQueue
|
||||
}
|
||||
|
||||
// IsTerminalIn returns true if t.In is a terminal. Does not check /dev/tty
|
||||
// even if TryDev is set.
|
||||
func (t TTY) IsTerminalIn() bool {
|
||||
return IsTerminal(t.In)
|
||||
}
|
||||
|
||||
// IsTerminalOut returns true if t.Out is a terminal. Does not check /dev/tty
|
||||
// even if TryDev is set.
|
||||
func (t TTY) IsTerminalOut() bool {
|
||||
return IsTerminal(t.Out)
|
||||
}
|
||||
|
||||
// IsTerminal returns whether the passed object is a terminal or not
|
||||
func IsTerminal(i interface{}) bool {
|
||||
_, terminal := term.GetFdInfo(i)
|
||||
return terminal
|
||||
}
|
||||
|
||||
// Safe invokes the provided function and will attempt to ensure that when the
|
||||
// function returns (or a termination signal is sent) that the terminal state
|
||||
// is reset to the condition it was in prior to the function being invoked. If
|
||||
// t.Raw is true the terminal will be put into raw mode prior to calling the function.
|
||||
// If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file
|
||||
// will be opened (if available).
|
||||
func (t TTY) Safe(fn SafeFunc) error {
|
||||
inFd, isTerminal := term.GetFdInfo(t.In)
|
||||
|
||||
if !isTerminal && t.TryDev {
|
||||
if f, err := os.Open("/dev/tty"); err == nil {
|
||||
defer f.Close()
|
||||
inFd = f.Fd()
|
||||
isTerminal = term.IsTerminal(inFd)
|
||||
}
|
||||
}
|
||||
if !isTerminal {
|
||||
return fn()
|
||||
}
|
||||
|
||||
var state *term.State
|
||||
var err error
|
||||
if t.Raw {
|
||||
state, err = term.MakeRaw(inFd)
|
||||
} else {
|
||||
state, err = term.SaveState(inFd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return interrupt.Chain(t.Parent, func() {
|
||||
if t.sizeQueue != nil {
|
||||
t.sizeQueue.stop()
|
||||
}
|
||||
|
||||
term.RestoreTerminal(inFd, state)
|
||||
}).Run(fn)
|
||||
}
|
||||
124
vendor/k8s.io/kubectl/pkg/util/term/term_writer.go
generated
vendored
Normal file
124
vendor/k8s.io/kubectl/pkg/util/term/term_writer.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2016 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 term
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
wordwrap "github.com/mitchellh/go-wordwrap"
|
||||
)
|
||||
|
||||
type wordWrapWriter struct {
|
||||
limit uint
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewResponsiveWriter creates a Writer that detects the column width of the
|
||||
// terminal we are in, and adjusts every line width to fit and use recommended
|
||||
// terminal sizes for better readability. Does proper word wrapping automatically.
|
||||
// if terminal width >= 120 columns use 120 columns
|
||||
// if terminal width >= 100 columns use 100 columns
|
||||
// if terminal width >= 80 columns use 80 columns
|
||||
// In case we're not in a terminal or if it's smaller than 80 columns width,
|
||||
// doesn't do any wrapping.
|
||||
func NewResponsiveWriter(w io.Writer) io.Writer {
|
||||
file, ok := w.(*os.File)
|
||||
if !ok {
|
||||
return w
|
||||
}
|
||||
fd := file.Fd()
|
||||
if !term.IsTerminal(fd) {
|
||||
return w
|
||||
}
|
||||
|
||||
terminalSize := GetSize(fd)
|
||||
if terminalSize == nil {
|
||||
return w
|
||||
}
|
||||
|
||||
var limit uint
|
||||
switch {
|
||||
case terminalSize.Width >= 120:
|
||||
limit = 120
|
||||
case terminalSize.Width >= 100:
|
||||
limit = 100
|
||||
case terminalSize.Width >= 80:
|
||||
limit = 80
|
||||
}
|
||||
|
||||
return NewWordWrapWriter(w, limit)
|
||||
}
|
||||
|
||||
// NewWordWrapWriter is a Writer that supports a limit of characters on every line
|
||||
// and does auto word wrapping that respects that limit.
|
||||
func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
|
||||
return &wordWrapWriter{
|
||||
limit: limit,
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (w wordWrapWriter) Write(p []byte) (nn int, err error) {
|
||||
if w.limit == 0 {
|
||||
return w.writer.Write(p)
|
||||
}
|
||||
original := string(p)
|
||||
wrapped := wordwrap.WrapString(original, w.limit)
|
||||
return w.writer.Write([]byte(wrapped))
|
||||
}
|
||||
|
||||
// NewPunchCardWriter is a NewWordWrapWriter that limits the line width to 80 columns.
|
||||
func NewPunchCardWriter(w io.Writer) io.Writer {
|
||||
return NewWordWrapWriter(w, 80)
|
||||
}
|
||||
|
||||
type maxWidthWriter struct {
|
||||
maxWidth uint
|
||||
currentWidth uint
|
||||
written uint
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewMaxWidthWriter is a Writer that supports a limit of characters on every
|
||||
// line, but doesn't do any word wrapping automatically.
|
||||
func NewMaxWidthWriter(w io.Writer, maxWidth uint) io.Writer {
|
||||
return &maxWidthWriter{
|
||||
maxWidth: maxWidth,
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (m maxWidthWriter) Write(p []byte) (nn int, err error) {
|
||||
for _, b := range p {
|
||||
if m.currentWidth == m.maxWidth {
|
||||
m.writer.Write([]byte{'\n'})
|
||||
m.currentWidth = 0
|
||||
}
|
||||
if b == '\n' {
|
||||
m.currentWidth = 0
|
||||
}
|
||||
_, err := m.writer.Write([]byte{b})
|
||||
if err != nil {
|
||||
return int(m.written), err
|
||||
}
|
||||
m.written++
|
||||
m.currentWidth++
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
103
vendor/k8s.io/kubectl/pkg/validation/schema.go
generated
vendored
Normal file
103
vendor/k8s.io/kubectl/pkg/validation/schema.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2014 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 validation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
ejson "github.com/exponent-io/jsonpath"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
// Schema is an interface that knows how to validate an API object serialized to a byte array.
|
||||
type Schema interface {
|
||||
ValidateBytes(data []byte) error
|
||||
}
|
||||
|
||||
// NullSchema always validates bytes.
|
||||
type NullSchema struct{}
|
||||
|
||||
// ValidateBytes never fails for NullSchema.
|
||||
func (NullSchema) ValidateBytes(data []byte) error { return nil }
|
||||
|
||||
// NoDoubleKeySchema is a schema that disallows double keys.
|
||||
type NoDoubleKeySchema struct{}
|
||||
|
||||
// ValidateBytes validates bytes.
|
||||
func (NoDoubleKeySchema) ValidateBytes(data []byte) error {
|
||||
var list []error
|
||||
if err := validateNoDuplicateKeys(data, "metadata", "labels"); err != nil {
|
||||
list = append(list, err)
|
||||
}
|
||||
if err := validateNoDuplicateKeys(data, "metadata", "annotations"); err != nil {
|
||||
list = append(list, err)
|
||||
}
|
||||
return utilerrors.NewAggregate(list)
|
||||
}
|
||||
|
||||
func validateNoDuplicateKeys(data []byte, path ...string) error {
|
||||
r := ejson.NewDecoder(bytes.NewReader(data))
|
||||
// This is Go being unfriendly. The 'path ...string' comes in as a
|
||||
// []string, and SeekTo takes ...interface{}, so we can't just pass
|
||||
// the path straight in, we have to copy it. *sigh*
|
||||
ifacePath := []interface{}{}
|
||||
for ix := range path {
|
||||
ifacePath = append(ifacePath, path[ix])
|
||||
}
|
||||
found, err := r.SeekTo(ifacePath...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for {
|
||||
tok, err := r.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case json.Delim:
|
||||
if t.String() == "}" {
|
||||
return nil
|
||||
}
|
||||
case ejson.KeyString:
|
||||
if seen[string(t)] {
|
||||
return fmt.Errorf("duplicate key: %s", string(t))
|
||||
}
|
||||
seen[string(t)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConjunctiveSchema encapsulates a schema list.
|
||||
type ConjunctiveSchema []Schema
|
||||
|
||||
// ValidateBytes validates bytes per a ConjunctiveSchema.
|
||||
func (c ConjunctiveSchema) ValidateBytes(data []byte) error {
|
||||
var list []error
|
||||
schemas := []Schema(c)
|
||||
for ix := range schemas {
|
||||
if err := schemas[ix].ValidateBytes(data); err != nil {
|
||||
list = append(list, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(list)
|
||||
}
|
||||
Reference in New Issue
Block a user