explain.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package kubectl
  14. import (
  15. "fmt"
  16. "io"
  17. "strings"
  18. "github.com/emicklei/go-restful/swagger"
  19. "k8s.io/kubernetes/pkg/api/meta"
  20. apiutil "k8s.io/kubernetes/pkg/api/util"
  21. )
  22. var allModels = make(map[string]*swagger.NamedModel)
  23. var recursive = false // this is global for convenience, can become int for multiple levels
  24. // SplitAndParseResourceRequest separates the users input into a model and fields
  25. func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) {
  26. inResource, fieldsPath := splitDotNotation(inResource)
  27. inResource, _ = mapper.ResourceSingularizer(inResource)
  28. return inResource, fieldsPath, nil
  29. }
  30. // PrintModelDescription prints the description of a specific model or dot path
  31. func PrintModelDescription(inModel string, fieldsPath []string, w io.Writer, swaggerSchema *swagger.ApiDeclaration, r bool) error {
  32. recursive = r // this is global for convenience
  33. apiVer := apiutil.GetVersion(swaggerSchema.ApiVersion) + "."
  34. var pointedModel *swagger.NamedModel
  35. for i := range swaggerSchema.Models.List {
  36. name := swaggerSchema.Models.List[i].Name
  37. allModels[name] = &swaggerSchema.Models.List[i]
  38. if strings.ToLower(name) == strings.ToLower(apiVer+inModel) {
  39. pointedModel = &swaggerSchema.Models.List[i]
  40. }
  41. }
  42. if pointedModel == nil {
  43. return fmt.Errorf("requested resource %q is not defined", inModel)
  44. }
  45. if len(fieldsPath) == 0 {
  46. return printTopLevelResourceInfo(w, pointedModel)
  47. }
  48. var pointedModelAsProp *swagger.NamedModelProperty
  49. for _, field := range fieldsPath {
  50. if prop, nextModel, isModel := getField(pointedModel, field); prop != nil {
  51. if isModel {
  52. pointedModelAsProp = prop
  53. pointedModel = allModels[nextModel]
  54. } else {
  55. return printPrimitive(w, prop)
  56. }
  57. } else {
  58. return fmt.Errorf("field %q does not exist", field)
  59. }
  60. }
  61. return printModelInfo(w, pointedModel, pointedModelAsProp)
  62. }
  63. func splitDotNotation(model string) (string, []string) {
  64. var fieldsPath []string
  65. dotModel := strings.Split(model, ".")
  66. if len(dotModel) >= 1 {
  67. fieldsPath = dotModel[1:]
  68. }
  69. return dotModel[0], fieldsPath
  70. }
  71. func getPointedModel(prop *swagger.ModelProperty) (string, bool) {
  72. if prop.Ref != nil {
  73. return *prop.Ref, true
  74. } else if *prop.Type == "array" && prop.Items.Ref != nil {
  75. return *prop.Items.Ref, true
  76. }
  77. return "", false
  78. }
  79. func getField(model *swagger.NamedModel, sField string) (*swagger.NamedModelProperty, string, bool) {
  80. for _, prop := range model.Model.Properties.List {
  81. if prop.Name == sField {
  82. pointedModel, isModel := getPointedModel(&prop.Property)
  83. return &prop, pointedModel, isModel
  84. }
  85. }
  86. return nil, "", false
  87. }
  88. func printModelInfo(w io.Writer, model *swagger.NamedModel, modelProp *swagger.NamedModelProperty) error {
  89. t, _ := getFieldType(&modelProp.Property)
  90. fmt.Fprintf(w, "RESOURCE: %s <%s>\n\n", modelProp.Name, t)
  91. fieldDesc, _ := wrapAndIndentText(modelProp.Property.Description, " ", 80)
  92. fmt.Fprintf(w, "DESCRIPTION:\n%s\n\n%s\n", fieldDesc, indentText(model.Model.Description, " "))
  93. return printFields(w, model)
  94. }
  95. func printPrimitive(w io.Writer, field *swagger.NamedModelProperty) error {
  96. t, _ := getFieldType(&field.Property)
  97. fmt.Fprintf(w, "FIELD: %s <%s>\n\n", field.Name, t)
  98. d, _ := wrapAndIndentText(field.Property.Description, " ", 80)
  99. fmt.Fprintf(w, "DESCRIPTION:\n%s\n", d)
  100. return nil
  101. }
  102. func printTopLevelResourceInfo(w io.Writer, model *swagger.NamedModel) error {
  103. fmt.Fprintf(w, "DESCRIPTION:\n%s\n", model.Model.Description)
  104. return printFields(w, model)
  105. }
  106. func printFields(w io.Writer, model *swagger.NamedModel) error {
  107. fmt.Fprint(w, "\nFIELDS:\n")
  108. for _, field := range model.Model.Properties.List {
  109. fieldType, err := getFieldType(&field.Property)
  110. if err != nil {
  111. return err
  112. }
  113. if arrayContains(model.Model.Required, field.Name) {
  114. fmt.Fprintf(w, " %s\t<%s> -required-\n", field.Name, fieldType)
  115. } else {
  116. fmt.Fprintf(w, " %s\t<%s>\n", field.Name, fieldType)
  117. }
  118. if recursive {
  119. pointedModel, isModel := getPointedModel(&field.Property)
  120. if isModel {
  121. for _, nestedField := range allModels[pointedModel].Model.Properties.List {
  122. t, _ := getFieldType(&nestedField.Property)
  123. fmt.Fprintf(w, " %s\t<%s>\n", nestedField.Name, t)
  124. }
  125. }
  126. } else {
  127. fieldDesc, _ := wrapAndIndentText(field.Property.Description, " ", 80)
  128. fmt.Fprintf(w, "%s\n\n", fieldDesc)
  129. }
  130. }
  131. fmt.Fprint(w, "\n")
  132. return nil
  133. }
  134. func getFieldType(prop *swagger.ModelProperty) (string, error) {
  135. if prop.Type == nil {
  136. return "Object", nil
  137. } else if *prop.Type == "any" {
  138. // Swagger Spec doesn't return information for maps.
  139. return "map[string]string", nil
  140. } else if *prop.Type == "array" {
  141. if prop.Items == nil {
  142. return "", fmt.Errorf("error in swagger spec. Property: %v contains an array without type", prop)
  143. }
  144. if prop.Items.Ref != nil {
  145. fieldType := "[]Object"
  146. return fieldType, nil
  147. }
  148. fieldType := "[]" + *prop.Items.Type
  149. return fieldType, nil
  150. }
  151. return *prop.Type, nil
  152. }
  153. func wrapAndIndentText(desc, indent string, lim int) (string, error) {
  154. words := strings.Split(strings.Replace(strings.TrimSpace(desc), "\n", " ", -1), " ")
  155. n := len(words)
  156. for i := 0; i < n; i++ {
  157. if len(words[i]) > lim {
  158. if strings.Contains(words[i], "/") {
  159. s := breakURL(words[i])
  160. words = append(words[:i], append(s, words[i+1:]...)...)
  161. i = i + len(s) - 1
  162. } else {
  163. fmt.Println(len(words[i]))
  164. return "", fmt.Errorf("there are words longer that the break limit is")
  165. }
  166. }
  167. }
  168. var lines []string
  169. line := []string{indent}
  170. lineL := len(indent)
  171. for i := 0; i < len(words); i++ {
  172. w := words[i]
  173. if strings.HasSuffix(w, "/") && lineL+len(w)-1 < lim {
  174. prev := line[len(line)-1]
  175. if strings.HasSuffix(prev, "/") {
  176. if i+1 < len(words)-1 && !strings.HasSuffix(words[i+1], "/") {
  177. w = strings.TrimSuffix(w, "/")
  178. }
  179. line[len(line)-1] = prev + w
  180. lineL += len(w)
  181. } else {
  182. line = append(line, w)
  183. lineL += len(w) + 1
  184. }
  185. } else if lineL+len(w) < lim {
  186. line = append(line, w)
  187. lineL += len(w) + 1
  188. } else {
  189. lines = append(lines, strings.Join(line, " "))
  190. line = []string{indent, w}
  191. lineL = len(indent) + len(w)
  192. }
  193. }
  194. lines = append(lines, strings.Join(line, " "))
  195. return strings.Join(lines, "\n"), nil
  196. }
  197. func breakURL(url string) []string {
  198. var buf []string
  199. for _, part := range strings.Split(url, "/") {
  200. buf = append(buf, part+"/")
  201. }
  202. return buf
  203. }
  204. func indentText(text, indent string) string {
  205. lines := strings.Split(text, "\n")
  206. for i := range lines {
  207. lines[i] = indent + lines[i]
  208. }
  209. return strings.Join(lines, "\n")
  210. }
  211. func arrayContains(s []string, e string) bool {
  212. for _, a := range s {
  213. if a == e {
  214. return true
  215. }
  216. }
  217. return false
  218. }