mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 18:00:08 +00:00
Rewrite kubectl explain
to use openapi
This removes all dependencies on swagger 1.2 for explain.
This commit is contained in:
parent
566364da49
commit
249caa95b5
@ -91,7 +91,6 @@ go_library(
|
|||||||
"deployment.go",
|
"deployment.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"env_file.go",
|
"env_file.go",
|
||||||
"explain.go",
|
|
||||||
"generate.go",
|
"generate.go",
|
||||||
"history.go",
|
"history.go",
|
||||||
"interfaces.go",
|
"interfaces.go",
|
||||||
@ -118,7 +117,6 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//federation/apis/federation/v1beta1:go_default_library",
|
"//federation/apis/federation/v1beta1:go_default_library",
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/api/util:go_default_library",
|
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/v1:go_default_library",
|
||||||
"//pkg/api/v1/pod:go_default_library",
|
"//pkg/api/v1/pod:go_default_library",
|
||||||
"//pkg/apis/apps:go_default_library",
|
"//pkg/apis/apps:go_default_library",
|
||||||
@ -142,7 +140,6 @@ go_library(
|
|||||||
"//pkg/kubectl/util/slice:go_default_library",
|
"//pkg/kubectl/util/slice:go_default_library",
|
||||||
"//pkg/printers:go_default_library",
|
"//pkg/printers:go_default_library",
|
||||||
"//pkg/printers/internalversion:go_default_library",
|
"//pkg/printers/internalversion:go_default_library",
|
||||||
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||||
@ -196,6 +193,7 @@ filegroup(
|
|||||||
":package-srcs",
|
":package-srcs",
|
||||||
"//pkg/kubectl/apps:all-srcs",
|
"//pkg/kubectl/apps:all-srcs",
|
||||||
"//pkg/kubectl/cmd:all-srcs",
|
"//pkg/kubectl/cmd:all-srcs",
|
||||||
|
"//pkg/kubectl/explain:all-srcs",
|
||||||
"//pkg/kubectl/metricsutil:all-srcs",
|
"//pkg/kubectl/metricsutil:all-srcs",
|
||||||
"//pkg/kubectl/plugins:all-srcs",
|
"//pkg/kubectl/plugins:all-srcs",
|
||||||
"//pkg/kubectl/proxy:all-srcs",
|
"//pkg/kubectl/proxy:all-srcs",
|
||||||
|
@ -84,6 +84,7 @@ go_library(
|
|||||||
"//pkg/kubectl/cmd/util:go_default_library",
|
"//pkg/kubectl/cmd/util:go_default_library",
|
||||||
"//pkg/kubectl/cmd/util/editor:go_default_library",
|
"//pkg/kubectl/cmd/util/editor:go_default_library",
|
||||||
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
||||||
|
"//pkg/kubectl/explain:go_default_library",
|
||||||
"//pkg/kubectl/metricsutil:go_default_library",
|
"//pkg/kubectl/metricsutil:go_default_library",
|
||||||
"//pkg/kubectl/plugins:go_default_library",
|
"//pkg/kubectl/plugins:go_default_library",
|
||||||
"//pkg/kubectl/proxy:go_default_library",
|
"//pkg/kubectl/proxy:go_default_library",
|
||||||
|
@ -24,9 +24,9 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/kubectl"
|
|
||||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/explain"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, ar
|
|||||||
// TODO: After we figured out the new syntax to separate group and resource, allow
|
// TODO: After we figured out the new syntax to separate group and resource, allow
|
||||||
// the users to use it in explain (kubectl explain <group><syntax><resource>).
|
// the users to use it in explain (kubectl explain <group><syntax><resource>).
|
||||||
// Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax.
|
// Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax.
|
||||||
inModel, fieldsPath, err := kubectl.SplitAndParseResourceRequest(args[0], mapper)
|
inModel, fieldsPath, err := explain.SplitAndParseResourceRequest(args[0], mapper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -108,14 +108,20 @@ func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, ar
|
|||||||
} else {
|
} else {
|
||||||
apiVersion, err = schema.ParseGroupVersion(apiVersionString)
|
apiVersion, err = schema.ParseGroupVersion(apiVersionString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gvk = apiVersion.WithKind(gvk.Kind)
|
||||||
|
|
||||||
schema, err := f.SwaggerSchema(apiVersion.WithKind(gvk.Kind))
|
resources, err := f.OpenAPISchema()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return kubectl.PrintModelDescription(inModel, fieldsPath, out, schema, recursive)
|
schema := resources.LookupResource(gvk)
|
||||||
|
if schema == nil {
|
||||||
|
return fmt.Errorf("Couldn't find resource for %q", gvk)
|
||||||
|
}
|
||||||
|
|
||||||
|
return explain.PrintModelDescription(fieldsPath, out, schema, recursive)
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package openapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -181,6 +182,26 @@ func (k *Kind) GetName() string {
|
|||||||
return fmt.Sprintf("Kind(%v)", properties)
|
return fmt.Sprintf("Kind(%v)", properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRequired returns true if `field` is a required field for this type.
|
||||||
|
func (k *Kind) IsRequired(field string) bool {
|
||||||
|
for _, f := range k.RequiredFields {
|
||||||
|
if f == field {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a alphabetically sorted list of keys.
|
||||||
|
func (k *Kind) Keys() []string {
|
||||||
|
keys := make([]string, 0)
|
||||||
|
for key := range k.Fields {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
// Map is an object who values must all be of the same `SubType`.
|
// Map is an object who values must all be of the same `SubType`.
|
||||||
// The key of the object is always of type `string`.
|
// The key of the object is always of type `string`.
|
||||||
type Map struct {
|
type Map struct {
|
||||||
|
@ -1,251 +0,0 @@
|
|||||||
/*
|
|
||||||
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 kubectl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
apiutil "k8s.io/kubernetes/pkg/api/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
var allModels = make(map[string]*swagger.NamedModel)
|
|
||||||
|
|
||||||
// SplitAndParseResourceRequest separates the users input into a model and fields
|
|
||||||
func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) {
|
|
||||||
inResource, fieldsPath := splitDotNotation(inResource)
|
|
||||||
inResource, _ = mapper.ResourceSingularizer(inResource)
|
|
||||||
return inResource, fieldsPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintModelDescription prints the description of a specific model or dot path.
|
|
||||||
// If recursive, all components nested within the fields of the schema will be
|
|
||||||
// printed.
|
|
||||||
func PrintModelDescription(inModel string, fieldsPath []string, w io.Writer, swaggerSchema *swagger.ApiDeclaration, recursive bool) error {
|
|
||||||
apiVer := apiutil.GetVersion(swaggerSchema.ApiVersion) + "."
|
|
||||||
|
|
||||||
var pointedModel *swagger.NamedModel
|
|
||||||
for i := range swaggerSchema.Models.List {
|
|
||||||
name := swaggerSchema.Models.List[i].Name
|
|
||||||
|
|
||||||
allModels[name] = &swaggerSchema.Models.List[i]
|
|
||||||
if strings.ToLower(name) == strings.ToLower(apiVer+inModel) {
|
|
||||||
pointedModel = &swaggerSchema.Models.List[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pointedModel == nil {
|
|
||||||
return fmt.Errorf("requested resource %q is not defined", inModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fieldsPath) == 0 {
|
|
||||||
return printTopLevelResourceInfo(w, pointedModel, recursive)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pointedModelAsProp *swagger.NamedModelProperty
|
|
||||||
for _, field := range fieldsPath {
|
|
||||||
if prop, nextModel, isModel := getField(pointedModel, field); prop != nil {
|
|
||||||
if isModel {
|
|
||||||
pointedModelAsProp = prop
|
|
||||||
pointedModel = allModels[nextModel]
|
|
||||||
} else {
|
|
||||||
return printPrimitive(w, prop)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("field %q does not exist", field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return printModelInfo(w, pointedModel, pointedModelAsProp, recursive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitDotNotation(model string) (string, []string) {
|
|
||||||
var fieldsPath []string
|
|
||||||
dotModel := strings.Split(model, ".")
|
|
||||||
if len(dotModel) >= 1 {
|
|
||||||
fieldsPath = dotModel[1:]
|
|
||||||
}
|
|
||||||
return dotModel[0], fieldsPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPointedModel(prop *swagger.ModelProperty) (string, bool) {
|
|
||||||
if prop.Ref != nil {
|
|
||||||
return *prop.Ref, true
|
|
||||||
} else if *prop.Type == "array" && prop.Items.Ref != nil {
|
|
||||||
return *prop.Items.Ref, true
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getField(model *swagger.NamedModel, sField string) (*swagger.NamedModelProperty, string, bool) {
|
|
||||||
for _, prop := range model.Model.Properties.List {
|
|
||||||
if prop.Name == sField {
|
|
||||||
pointedModel, isModel := getPointedModel(&prop.Property)
|
|
||||||
return &prop, pointedModel, isModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func printModelInfo(w io.Writer, model *swagger.NamedModel, modelProp *swagger.NamedModelProperty, recursive bool) error {
|
|
||||||
t, _ := getFieldType(&modelProp.Property)
|
|
||||||
fmt.Fprintf(w, "RESOURCE: %s <%s>\n\n", modelProp.Name, t)
|
|
||||||
fieldDesc, _ := wrapAndIndentText(modelProp.Property.Description, " ", 80)
|
|
||||||
fmt.Fprintf(w, "DESCRIPTION:\n%s\n\n%s\n", fieldDesc, indentText(model.Model.Description, " "))
|
|
||||||
return printFields(w, model, recursive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printPrimitive(w io.Writer, field *swagger.NamedModelProperty) error {
|
|
||||||
t, _ := getFieldType(&field.Property)
|
|
||||||
fmt.Fprintf(w, "FIELD: %s <%s>\n\n", field.Name, t)
|
|
||||||
d, _ := wrapAndIndentText(field.Property.Description, " ", 80)
|
|
||||||
fmt.Fprintf(w, "DESCRIPTION:\n%s\n", d)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTopLevelResourceInfo(w io.Writer, model *swagger.NamedModel, recursive bool) error {
|
|
||||||
fmt.Fprintf(w, "DESCRIPTION:\n%s\n", model.Model.Description)
|
|
||||||
return printFields(w, model, recursive)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printFields(w io.Writer, model *swagger.NamedModel, recursive bool) error {
|
|
||||||
fmt.Fprint(w, "\nFIELDS:\n")
|
|
||||||
for _, field := range model.Model.Properties.List {
|
|
||||||
fieldType, err := getFieldType(&field.Property)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if arrayContains(model.Model.Required, field.Name) {
|
|
||||||
fmt.Fprintf(w, " %s\t<%s> -required-\n", field.Name, fieldType)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, " %s\t<%s>\n", field.Name, fieldType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if recursive {
|
|
||||||
pointedModel, isModel := getPointedModel(&field.Property)
|
|
||||||
if isModel {
|
|
||||||
for _, nestedField := range allModels[pointedModel].Model.Properties.List {
|
|
||||||
t, _ := getFieldType(&nestedField.Property)
|
|
||||||
fmt.Fprintf(w, " %s\t<%s>\n", nestedField.Name, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fieldDesc, _ := wrapAndIndentText(field.Property.Description, " ", 80)
|
|
||||||
fmt.Fprintf(w, "%s\n\n", fieldDesc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, "\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFieldType(prop *swagger.ModelProperty) (string, error) {
|
|
||||||
if prop.Type == nil {
|
|
||||||
return "Object", nil
|
|
||||||
} else if *prop.Type == "any" {
|
|
||||||
// Swagger Spec doesn't return information for maps.
|
|
||||||
return "map[string]string", nil
|
|
||||||
} else if *prop.Type == "array" {
|
|
||||||
if prop.Items == nil {
|
|
||||||
return "", fmt.Errorf("error in swagger spec. Property: %v contains an array without type", prop)
|
|
||||||
}
|
|
||||||
if prop.Items.Ref != nil {
|
|
||||||
fieldType := "[]Object"
|
|
||||||
return fieldType, nil
|
|
||||||
}
|
|
||||||
fieldType := "[]" + *prop.Items.Type
|
|
||||||
return fieldType, nil
|
|
||||||
}
|
|
||||||
return *prop.Type, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapAndIndentText(desc, indent string, lim int) (string, error) {
|
|
||||||
words := strings.Split(strings.Replace(strings.TrimSpace(desc), "\n", " ", -1), " ")
|
|
||||||
n := len(words)
|
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
if len(words[i]) > lim {
|
|
||||||
if strings.Contains(words[i], "/") {
|
|
||||||
s := breakURL(words[i])
|
|
||||||
words = append(words[:i], append(s, words[i+1:]...)...)
|
|
||||||
i = i + len(s) - 1
|
|
||||||
} else {
|
|
||||||
fmt.Println(len(words[i]))
|
|
||||||
return "", fmt.Errorf("there are words longer that the break limit is")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines []string
|
|
||||||
line := []string{indent}
|
|
||||||
lineL := len(indent)
|
|
||||||
for i := 0; i < len(words); i++ {
|
|
||||||
w := words[i]
|
|
||||||
|
|
||||||
if strings.HasSuffix(w, "/") && lineL+len(w)-1 < lim {
|
|
||||||
prev := line[len(line)-1]
|
|
||||||
if strings.HasSuffix(prev, "/") {
|
|
||||||
if i+1 < len(words)-1 && !strings.HasSuffix(words[i+1], "/") {
|
|
||||||
w = strings.TrimSuffix(w, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
line[len(line)-1] = prev + w
|
|
||||||
lineL += len(w)
|
|
||||||
} else {
|
|
||||||
line = append(line, w)
|
|
||||||
lineL += len(w) + 1
|
|
||||||
}
|
|
||||||
} else if lineL+len(w) < lim {
|
|
||||||
line = append(line, w)
|
|
||||||
lineL += len(w) + 1
|
|
||||||
} else {
|
|
||||||
lines = append(lines, strings.Join(line, " "))
|
|
||||||
line = []string{indent, w}
|
|
||||||
lineL = len(indent) + len(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines = append(lines, strings.Join(line, " "))
|
|
||||||
|
|
||||||
return strings.Join(lines, "\n"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func breakURL(url string) []string {
|
|
||||||
var buf []string
|
|
||||||
for _, part := range strings.Split(url, "/") {
|
|
||||||
buf = append(buf, part+"/")
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func indentText(text, indent string) string {
|
|
||||||
lines := strings.Split(text, "\n")
|
|
||||||
for i := range lines {
|
|
||||||
lines[i] = indent + lines[i]
|
|
||||||
}
|
|
||||||
return strings.Join(lines, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func arrayContains(s []string, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if a == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
34
pkg/kubectl/explain/BUILD
Normal file
34
pkg/kubectl/explain/BUILD
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"explain.go",
|
||||||
|
"field_lookup.go",
|
||||||
|
"fields_printer.go",
|
||||||
|
"fields_printer_builder.go",
|
||||||
|
"formatter.go",
|
||||||
|
"model_printer.go",
|
||||||
|
"recursive_fields_printer.go",
|
||||||
|
"typename.go",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/kubectl/cmd/util/openapi:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
64
pkg/kubectl/explain/explain.go
Normal file
64
pkg/kubectl/explain/explain.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fieldsPrinter interface {
|
||||||
|
PrintFields(openapi.Schema) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitDotNotation(model string) (string, []string) {
|
||||||
|
var fieldsPath []string
|
||||||
|
dotModel := strings.Split(model, ".")
|
||||||
|
if len(dotModel) >= 1 {
|
||||||
|
fieldsPath = dotModel[1:]
|
||||||
|
}
|
||||||
|
return dotModel[0], fieldsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitAndParseResourceRequest separates the users input into a model and fields
|
||||||
|
func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) {
|
||||||
|
inResource, fieldsPath := splitDotNotation(inResource)
|
||||||
|
inResource, _ = mapper.ResourceSingularizer(inResource)
|
||||||
|
return inResource, fieldsPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintModelDescription prints the description of a specific model or dot path.
|
||||||
|
// If recursive, all components nested within the fields of the schema will be
|
||||||
|
// printed.
|
||||||
|
func PrintModelDescription(fieldsPath []string, w io.Writer, schema openapi.Schema, recursive bool) error {
|
||||||
|
fieldName := ""
|
||||||
|
if len(fieldsPath) != 0 {
|
||||||
|
fieldName = fieldsPath[len(fieldsPath)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go down the fieldsPath to find what we're trying to explain
|
||||||
|
schema, err := LookupSchemaForField(schema, fieldsPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b := fieldsPrinterBuilder{Recursive: recursive}
|
||||||
|
f := &Formatter{Writer: w, Wrap: 80}
|
||||||
|
return PrintModel(fieldName, f, b, schema)
|
||||||
|
}
|
107
pkg/kubectl/explain/field_lookup.go
Normal file
107
pkg/kubectl/explain/field_lookup.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fieldLookup walks through a schema by following a path, and returns
|
||||||
|
// the final schema.
|
||||||
|
type fieldLookup struct {
|
||||||
|
// Path to walk
|
||||||
|
Path []string
|
||||||
|
|
||||||
|
// Return information: Schema found, or error.
|
||||||
|
Schema openapi.Schema
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveLeafSchema is used to detect if we are done walking the path, and
|
||||||
|
// saves the schema as a match.
|
||||||
|
func (f *fieldLookup) SaveLeafSchema(schema openapi.Schema) bool {
|
||||||
|
if len(f.Path) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Schema = schema
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitArray is mostly a passthrough.
|
||||||
|
func (f *fieldLookup) VisitArray(a *openapi.Array) {
|
||||||
|
if f.SaveLeafSchema(a) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passthrough arrays.
|
||||||
|
a.SubType.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitMap is mostly a passthrough.
|
||||||
|
func (f *fieldLookup) VisitMap(m *openapi.Map) {
|
||||||
|
if f.SaveLeafSchema(m) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passthrough maps.
|
||||||
|
m.SubType.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPrimitive stops the operation and returns itself as the found
|
||||||
|
// schema, even if it had more path to walk.
|
||||||
|
func (f *fieldLookup) VisitPrimitive(p *openapi.Primitive) {
|
||||||
|
// Even if Path is not empty (we're not expecting a leaf),
|
||||||
|
// return that primitive.
|
||||||
|
f.Schema = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitKind unstacks fields as it finds them.
|
||||||
|
func (f *fieldLookup) VisitKind(k *openapi.Kind) {
|
||||||
|
if f.SaveLeafSchema(k) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subSchema, ok := k.Fields[f.Path[0]]
|
||||||
|
if !ok {
|
||||||
|
f.Error = fmt.Errorf("field %q does not exist", f.Path[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Path = f.Path[1:]
|
||||||
|
subSchema.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitReference is mostly a passthrough.
|
||||||
|
func (f *fieldLookup) VisitReference(r openapi.Reference) {
|
||||||
|
if f.SaveLeafSchema(r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passthrough references.
|
||||||
|
r.SubSchema().Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupSchemaForField looks for the schema of a given path in a base schema.
|
||||||
|
func LookupSchemaForField(schema openapi.Schema, path []string) (openapi.Schema, error) {
|
||||||
|
f := &fieldLookup{Path: path}
|
||||||
|
schema.Accept(f)
|
||||||
|
return f.Schema, f.Error
|
||||||
|
}
|
84
pkg/kubectl/explain/fields_printer.go
Normal file
84
pkg/kubectl/explain/fields_printer.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// indentDesc is the level of indentation for descriptions.
|
||||||
|
const indentDesc = 2
|
||||||
|
|
||||||
|
// regularFieldsPrinter prints fields with their type and description.
|
||||||
|
type regularFieldsPrinter struct {
|
||||||
|
Writer *Formatter
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ openapi.SchemaVisitor = ®ularFieldsPrinter{}
|
||||||
|
var _ fieldsPrinter = ®ularFieldsPrinter{}
|
||||||
|
|
||||||
|
// VisitArray prints a Array type. It is just a passthrough.
|
||||||
|
func (f *regularFieldsPrinter) VisitArray(a *openapi.Array) {
|
||||||
|
a.SubType.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitKind prints a Kind type. It prints each key in the kind, with
|
||||||
|
// the type, the required flag, and the description.
|
||||||
|
func (f *regularFieldsPrinter) VisitKind(k *openapi.Kind) {
|
||||||
|
for _, key := range k.Keys() {
|
||||||
|
v := k.Fields[key]
|
||||||
|
required := ""
|
||||||
|
if k.IsRequired(key) {
|
||||||
|
required = " -required-"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.Writer.Write("%s\t<%s>%s", key, GetTypeName(v), required); err != nil {
|
||||||
|
f.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := f.Writer.Indent(indentDesc).WriteWrapped("%s", v.GetDescription()); err != nil {
|
||||||
|
f.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := f.Writer.Write(""); err != nil {
|
||||||
|
f.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitMap prints a Map type. It is just a passthrough.
|
||||||
|
func (f *regularFieldsPrinter) VisitMap(m *openapi.Map) {
|
||||||
|
m.SubType.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPrimitive prints a Primitive type. It stops the recursion.
|
||||||
|
func (f *regularFieldsPrinter) VisitPrimitive(p *openapi.Primitive) {
|
||||||
|
// Nothing to do. Shouldn't really happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitReference prints a Reference type. It is just a passthrough.
|
||||||
|
func (f *regularFieldsPrinter) VisitReference(r openapi.Reference) {
|
||||||
|
r.SubSchema().Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintFields will write the types from schema.
|
||||||
|
func (f *regularFieldsPrinter) PrintFields(schema openapi.Schema) error {
|
||||||
|
schema.Accept(f)
|
||||||
|
return f.Error
|
||||||
|
}
|
36
pkg/kubectl/explain/fields_printer_builder.go
Normal file
36
pkg/kubectl/explain/fields_printer_builder.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
// fieldsPrinterBuilder builds either a regularFieldsPrinter or a
|
||||||
|
// recursiveFieldsPrinter based on the argument.
|
||||||
|
type fieldsPrinterBuilder struct {
|
||||||
|
Recursive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildFieldsPrinter builds the appropriate fieldsPrinter.
|
||||||
|
func (f fieldsPrinterBuilder) BuildFieldsPrinter(writer *Formatter) fieldsPrinter {
|
||||||
|
if f.Recursive {
|
||||||
|
return &recursiveFieldsPrinter{
|
||||||
|
Writer: writer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ®ularFieldsPrinter{
|
||||||
|
Writer: writer,
|
||||||
|
}
|
||||||
|
}
|
120
pkg/kubectl/explain/formatter.go
Normal file
120
pkg/kubectl/explain/formatter.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Formatter helps you write with indentation, and can wrap text as needed.
|
||||||
|
type Formatter struct {
|
||||||
|
IndentLevel int
|
||||||
|
Wrap int
|
||||||
|
Writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indent creates a new Formatter that will indent the code by that much more.
|
||||||
|
func (f Formatter) Indent(indent int) *Formatter {
|
||||||
|
f.IndentLevel = f.IndentLevel + indent
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes a string with the indentation set for the
|
||||||
|
// Formatter. This is not wrapping text.
|
||||||
|
func (f *Formatter) Write(str string, a ...interface{}) error {
|
||||||
|
// Don't indent empty lines
|
||||||
|
if str == "" {
|
||||||
|
_, err := io.WriteString(f.Writer, "\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indent := ""
|
||||||
|
for i := 0; i < f.IndentLevel; i++ {
|
||||||
|
indent = indent + " "
|
||||||
|
}
|
||||||
|
_, err := io.WriteString(f.Writer, indent+fmt.Sprintf(str, a...)+"\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteWrapped writes a string with the indentation set for the
|
||||||
|
// Formatter, and wraps as needed.
|
||||||
|
func (f *Formatter) WriteWrapped(str string, a ...interface{}) error {
|
||||||
|
if f.Wrap == 0 {
|
||||||
|
return f.Write(str, a...)
|
||||||
|
}
|
||||||
|
text := fmt.Sprintf(str, a...)
|
||||||
|
strs := wrapString(text, f.Wrap-f.IndentLevel)
|
||||||
|
for _, substr := range strs {
|
||||||
|
if err := f.Write(substr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type line struct {
|
||||||
|
wrap int
|
||||||
|
words []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *line) String() string {
|
||||||
|
return strings.Join(l.words, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *line) Empty() bool {
|
||||||
|
return len(l.words) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *line) Len() int {
|
||||||
|
return len(l.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the word to the line, returns true if we could, false if we
|
||||||
|
// didn't have enough room. It's always possible to add to an empty line.
|
||||||
|
func (l *line) Add(word string) bool {
|
||||||
|
newLine := line{
|
||||||
|
wrap: l.wrap,
|
||||||
|
words: append(l.words, word),
|
||||||
|
}
|
||||||
|
if newLine.Len() <= l.wrap || len(l.words) == 0 {
|
||||||
|
l.words = newLine.words
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapString(str string, wrap int) []string {
|
||||||
|
words := strings.Fields(str)
|
||||||
|
wrapped := []string{}
|
||||||
|
l := line{wrap: wrap}
|
||||||
|
|
||||||
|
for _, word := range words {
|
||||||
|
if l.Add(word) == false {
|
||||||
|
wrapped = append(wrapped, l.String())
|
||||||
|
l = line{wrap: wrap}
|
||||||
|
if l.Add(word) == false {
|
||||||
|
panic("Couldn't add to empty line.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !l.Empty() {
|
||||||
|
wrapped = append(wrapped, l.String())
|
||||||
|
}
|
||||||
|
return wrapped
|
||||||
|
}
|
129
pkg/kubectl/explain/model_printer.go
Normal file
129
pkg/kubectl/explain/model_printer.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fieldIndentLevel is the level of indentation for fields.
|
||||||
|
const fieldIndentLevel = 3
|
||||||
|
|
||||||
|
// descriptionIndentLevel is the level of indentation for the
|
||||||
|
// description.
|
||||||
|
const descriptionIndentLevel = 5
|
||||||
|
|
||||||
|
// modelPrinter prints a schema in Writer. Its "Builder" will decide if
|
||||||
|
// it's recursive or not.
|
||||||
|
type modelPrinter struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Descriptions []string
|
||||||
|
Writer *Formatter
|
||||||
|
Builder fieldsPrinterBuilder
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ openapi.SchemaVisitor = &modelPrinter{}
|
||||||
|
|
||||||
|
// PrintDescription prints the description for a given schema. There
|
||||||
|
// might be multiple description, since we collect descriptions when we
|
||||||
|
// go through references, arrays and maps.
|
||||||
|
func (m *modelPrinter) PrintDescription(schema openapi.Schema) error {
|
||||||
|
if err := m.Writer.Write("DESCRIPTION:"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, desc := range append(m.Descriptions, schema.GetDescription()) {
|
||||||
|
if desc == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i != 0 {
|
||||||
|
if err := m.Writer.Write(""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := m.Writer.Indent(descriptionIndentLevel).WriteWrapped(desc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitArray recurses inside the subtype, while collecting the type if
|
||||||
|
// not done yet, and the description.
|
||||||
|
func (m *modelPrinter) VisitArray(a *openapi.Array) {
|
||||||
|
m.Descriptions = append(m.Descriptions, a.GetDescription())
|
||||||
|
if m.Type == "" {
|
||||||
|
m.Type = GetTypeName(a)
|
||||||
|
}
|
||||||
|
a.SubType.Accept(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitKind prints a full resource with its fields.
|
||||||
|
func (m *modelPrinter) VisitKind(k *openapi.Kind) {
|
||||||
|
if m.Type == "" {
|
||||||
|
m.Type = GetTypeName(k)
|
||||||
|
}
|
||||||
|
if m.Name != "" {
|
||||||
|
m.Writer.Write("RESOURCE: %s <%s>\n", m.Name, m.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.PrintDescription(k); err != nil {
|
||||||
|
m.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := m.Writer.Write("\nFIELDS:"); err != nil {
|
||||||
|
m.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Error = m.Builder.BuildFieldsPrinter(m.Writer.Indent(fieldIndentLevel)).PrintFields(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitMap recurses inside the subtype, while collecting the type if
|
||||||
|
// not done yet, and the description.
|
||||||
|
func (m *modelPrinter) VisitMap(om *openapi.Map) {
|
||||||
|
m.Descriptions = append(m.Descriptions, om.GetDescription())
|
||||||
|
if m.Type == "" {
|
||||||
|
m.Type = GetTypeName(om)
|
||||||
|
}
|
||||||
|
om.SubType.Accept(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPrimitive prints a field type and its description.
|
||||||
|
func (m *modelPrinter) VisitPrimitive(p *openapi.Primitive) {
|
||||||
|
if m.Type == "" {
|
||||||
|
m.Type = GetTypeName(p)
|
||||||
|
}
|
||||||
|
if err := m.Writer.Write("FIELD: %s <%s>\n", m.Name, m.Type); err != nil {
|
||||||
|
m.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Error = m.PrintDescription(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitReference recurses inside the subtype, while collecting the description.
|
||||||
|
func (m *modelPrinter) VisitReference(r openapi.Reference) {
|
||||||
|
m.Descriptions = append(m.Descriptions, r.GetDescription())
|
||||||
|
r.SubSchema().Accept(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintModel prints the description of a schema in writer.
|
||||||
|
func PrintModel(name string, writer *Formatter, builder fieldsPrinterBuilder, schema openapi.Schema) error {
|
||||||
|
m := &modelPrinter{Name: name, Writer: writer, Builder: builder}
|
||||||
|
schema.Accept(m)
|
||||||
|
return m.Error
|
||||||
|
}
|
77
pkg/kubectl/explain/recursive_fields_printer.go
Normal file
77
pkg/kubectl/explain/recursive_fields_printer.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// indentPerLevel is the level of indentation for each field recursion.
|
||||||
|
const indentPerLevel = 3
|
||||||
|
|
||||||
|
// recursiveFieldsPrinter recursively prints all the fields for a given
|
||||||
|
// schema.
|
||||||
|
type recursiveFieldsPrinter struct {
|
||||||
|
Writer *Formatter
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ openapi.SchemaVisitor = &recursiveFieldsPrinter{}
|
||||||
|
var _ fieldsPrinter = &recursiveFieldsPrinter{}
|
||||||
|
|
||||||
|
// VisitArray is just a passthrough.
|
||||||
|
func (f *recursiveFieldsPrinter) VisitArray(a *openapi.Array) {
|
||||||
|
a.SubType.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitKind prints all its fields with their type, and then recurses
|
||||||
|
// inside each of these (pre-order).
|
||||||
|
func (f *recursiveFieldsPrinter) VisitKind(k *openapi.Kind) {
|
||||||
|
for _, key := range k.Keys() {
|
||||||
|
v := k.Fields[key]
|
||||||
|
f.Writer.Write("%s\t<%s>", key, GetTypeName(v))
|
||||||
|
subFields := &recursiveFieldsPrinter{
|
||||||
|
Writer: f.Writer.Indent(indentPerLevel),
|
||||||
|
}
|
||||||
|
if err := subFields.PrintFields(v); err != nil {
|
||||||
|
f.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitMap is just a passthrough.
|
||||||
|
func (f *recursiveFieldsPrinter) VisitMap(m *openapi.Map) {
|
||||||
|
m.SubType.Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPrimitive does nothing, since it doesn't have sub-fields.
|
||||||
|
func (f *recursiveFieldsPrinter) VisitPrimitive(p *openapi.Primitive) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitReference is just a passthrough.
|
||||||
|
func (f *recursiveFieldsPrinter) VisitReference(r openapi.Reference) {
|
||||||
|
r.SubSchema().Accept(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintFields will recursively print all the fields for the given
|
||||||
|
// schema.
|
||||||
|
func (f *recursiveFieldsPrinter) PrintFields(schema openapi.Schema) error {
|
||||||
|
schema.Accept(f)
|
||||||
|
return f.Error
|
||||||
|
}
|
66
pkg/kubectl/explain/typename.go
Normal file
66
pkg/kubectl/explain/typename.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
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 explain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// typeName finds the name of a schema
|
||||||
|
type typeName struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ openapi.SchemaVisitor = &typeName{}
|
||||||
|
|
||||||
|
// VisitArray adds the [] prefix and recurses.
|
||||||
|
func (t *typeName) VisitArray(a *openapi.Array) {
|
||||||
|
s := &typeName{}
|
||||||
|
a.SubType.Accept(s)
|
||||||
|
t.Name = fmt.Sprintf("[]%s", s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitKind just returns "Object".
|
||||||
|
func (t *typeName) VisitKind(k *openapi.Kind) {
|
||||||
|
t.Name = "Object"
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitMap adds the map[string] prefix and recurses.
|
||||||
|
func (t *typeName) VisitMap(m *openapi.Map) {
|
||||||
|
s := &typeName{}
|
||||||
|
m.SubType.Accept(s)
|
||||||
|
t.Name = fmt.Sprintf("map[string]%s", s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPrimitive returns the name of the primitive.
|
||||||
|
func (t *typeName) VisitPrimitive(p *openapi.Primitive) {
|
||||||
|
t.Name = p.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitReference is just a passthrough.
|
||||||
|
func (t *typeName) VisitReference(r openapi.Reference) {
|
||||||
|
r.SubSchema().Accept(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTypeName returns the type of a schema.
|
||||||
|
func GetTypeName(schema openapi.Schema) string {
|
||||||
|
t := &typeName{}
|
||||||
|
schema.Accept(t)
|
||||||
|
return t.Name
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user