mirror of https://github.com/k3s-io/k3s
Rewrite `kubectl explain` to use openapi
This removes all dependencies on swagger 1.2 for explain.pull/6/head
parent
566364da49
commit
249caa95b5
|
@ -91,7 +91,6 @@ go_library(
|
|||
"deployment.go",
|
||||
"doc.go",
|
||||
"env_file.go",
|
||||
"explain.go",
|
||||
"generate.go",
|
||||
"history.go",
|
||||
"interfaces.go",
|
||||
|
@ -118,7 +117,6 @@ go_library(
|
|||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/util:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/api/v1/pod:go_default_library",
|
||||
"//pkg/apis/apps:go_default_library",
|
||||
|
@ -142,7 +140,6 @@ go_library(
|
|||
"//pkg/kubectl/util/slice:go_default_library",
|
||||
"//pkg/printers: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/spf13/cobra:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
|
@ -196,6 +193,7 @@ filegroup(
|
|||
":package-srcs",
|
||||
"//pkg/kubectl/apps:all-srcs",
|
||||
"//pkg/kubectl/cmd:all-srcs",
|
||||
"//pkg/kubectl/explain:all-srcs",
|
||||
"//pkg/kubectl/metricsutil:all-srcs",
|
||||
"//pkg/kubectl/plugins:all-srcs",
|
||||
"//pkg/kubectl/proxy:all-srcs",
|
||||
|
|
|
@ -84,6 +84,7 @@ go_library(
|
|||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubectl/cmd/util/editor: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/plugins:go_default_library",
|
||||
"//pkg/kubectl/proxy:go_default_library",
|
||||
|
|
|
@ -24,9 +24,9 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/explain"
|
||||
"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
|
||||
// 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.
|
||||
inModel, fieldsPath, err := kubectl.SplitAndParseResourceRequest(args[0], mapper)
|
||||
inModel, fieldsPath, err := explain.SplitAndParseResourceRequest(args[0], mapper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,14 +108,20 @@ func RunExplain(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, ar
|
|||
} else {
|
||||
apiVersion, err = schema.ParseGroupVersion(apiVersionString)
|
||||
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 {
|
||||
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 (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
@ -181,6 +182,26 @@ func (k *Kind) GetName() string {
|
|||
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`.
|
||||
// The key of the object is always of type `string`.
|
||||
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
|
||||
}
|
|
@ -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"],
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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