kubetestgen.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. Copyright 2019 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 main
  14. import (
  15. "flag"
  16. "fmt"
  17. "os"
  18. "sort"
  19. "strings"
  20. "github.com/go-openapi/analysis"
  21. "github.com/go-openapi/loads"
  22. "github.com/go-openapi/spec"
  23. "gopkg.in/yaml.v2"
  24. "k8s.io/kubernetes/test/conformance/behaviors"
  25. )
  26. type options struct {
  27. schemaPath string
  28. resource string
  29. area string
  30. behaviorsDir string
  31. }
  32. func parseFlags() *options {
  33. o := &options{}
  34. flag.StringVar(&o.schemaPath, "schema", "", "Path to the OpenAPI schema")
  35. flag.StringVar(&o.resource, "resource", ".*", "Resource name")
  36. flag.StringVar(&o.area, "area", "default", "Area name to use")
  37. flag.StringVar(&o.behaviorsDir, "dir", "../behaviors/", "Path to the behaviors directory")
  38. flag.Parse()
  39. return o
  40. }
  41. var defMap map[string]analysis.SchemaRef
  42. func main() {
  43. defMap = make(map[string]analysis.SchemaRef)
  44. o := parseFlags()
  45. d, err := loads.JSONSpec(o.schemaPath)
  46. if err != nil {
  47. fmt.Printf("ERROR: %s\n", err.Error())
  48. os.Exit(1)
  49. }
  50. defs := d.Analyzer.AllDefinitions()
  51. sort.Slice(defs, func(i, j int) bool { return defs[i].Name < defs[j].Name })
  52. for _, d := range defs {
  53. if !d.TopLevel {
  54. continue
  55. }
  56. defMap[d.Ref.String()] = d
  57. }
  58. var suites []behaviors.Suite
  59. var suiteMapping = make(map[string]*behaviors.Suite)
  60. for _, v := range defs {
  61. if !v.TopLevel || o.resource != v.Name {
  62. continue
  63. }
  64. name := trimObjectName(v.Name)
  65. defaultsuite := behaviors.Suite{
  66. Suite: o.area + "/spec",
  67. Description: "Base suite for " + o.area,
  68. Behaviors: []behaviors.Behavior{},
  69. }
  70. _ = defaultsuite
  71. for p, propSchema := range v.Schema.Properties {
  72. id := o.area + p + "/"
  73. if propSchema.Ref.String() != "" || propSchema.Type[0] == "array" {
  74. if _, ok := suiteMapping[id]; !ok {
  75. newsuite := behaviors.Suite{
  76. Suite: o.area + "/" + p,
  77. Description: "Suite for " + o.area + "/" + p,
  78. Behaviors: []behaviors.Behavior{},
  79. }
  80. suiteMapping[id] = &newsuite
  81. }
  82. behaviors := suiteMapping[id].Behaviors
  83. behaviors = append(behaviors, schemaBehavior(o.area, name, p, propSchema)...)
  84. suiteMapping[id].Behaviors = behaviors
  85. } else {
  86. if _, ok := suiteMapping["default"]; !ok {
  87. newsuite := behaviors.Suite{
  88. Suite: o.area + "/spec",
  89. Description: "Base suite for " + o.area,
  90. Behaviors: []behaviors.Behavior{},
  91. }
  92. suiteMapping["default"] = &newsuite
  93. }
  94. behaviors := suiteMapping["default"].Behaviors
  95. behaviors = append(behaviors, schemaBehavior(o.area, name, p, propSchema)...)
  96. suiteMapping["default"].Behaviors = behaviors
  97. }
  98. }
  99. for _, v := range suiteMapping {
  100. suites = append(suites, *v)
  101. }
  102. break
  103. }
  104. var area behaviors.Area = behaviors.Area{Area: o.area, Suites: suites}
  105. countFields(suites)
  106. printYAML(o.behaviorsDir+o.area, area)
  107. }
  108. func printYAML(fileName string, areaO behaviors.Area) {
  109. f, err := os.Create(fileName + ".yaml")
  110. if err != nil {
  111. fmt.Printf("ERROR: %s\n", err.Error())
  112. os.Exit(1)
  113. }
  114. defer f.Close()
  115. y, err := yaml.Marshal(areaO)
  116. if err != nil {
  117. fmt.Printf("ERROR: %s\n", err.Error())
  118. os.Exit(1)
  119. }
  120. _, err = f.WriteString(string(y))
  121. if err != nil {
  122. fmt.Printf("ERROR: %s\n", err.Error())
  123. os.Exit(1)
  124. }
  125. }
  126. func countFields(suites []behaviors.Suite) {
  127. var fieldsMapping map[string]int
  128. fieldsMapping = make(map[string]int)
  129. for _, suite := range suites {
  130. for _, behavior := range suite.Behaviors {
  131. if _, exists := fieldsMapping[behavior.APIType]; exists {
  132. fieldsMapping[behavior.APIType]++
  133. } else {
  134. fieldsMapping[behavior.APIType] = 1
  135. }
  136. }
  137. }
  138. for k, v := range fieldsMapping {
  139. fmt.Printf("Type %v, Count %v\n", k, v)
  140. }
  141. }
  142. func trimObjectName(name string) string {
  143. if strings.Index(name, "#/definitions/") == 0 {
  144. name = name[len("#/definitions/"):]
  145. }
  146. if strings.Index(name, "io.k8s.api.") == 0 {
  147. return name[len("io.k8s.api."):]
  148. }
  149. return name
  150. }
  151. func objectBehaviors(id string, s *spec.Schema) []behaviors.Behavior {
  152. if strings.Contains(id, "openAPIV3Schema") || strings.Contains(id, "JSONSchema") || strings.Contains(s.Ref.String(), "JSONSchema") {
  153. return []behaviors.Behavior{}
  154. }
  155. ref, ok := defMap[s.Ref.String()]
  156. if !ok {
  157. return []behaviors.Behavior{}
  158. }
  159. return schemaBehaviors(id, trimObjectName(ref.Name), ref.Schema)
  160. }
  161. func schemaBehaviors(base, apiObject string, s *spec.Schema) []behaviors.Behavior {
  162. var behaviors []behaviors.Behavior
  163. for p, propSchema := range s.Properties {
  164. b := schemaBehavior(base, apiObject, p, propSchema)
  165. behaviors = append(behaviors, b...)
  166. }
  167. return behaviors
  168. }
  169. func schemaBehavior(base, apiObject, p string, propSchema spec.Schema) []behaviors.Behavior {
  170. id := strings.Join([]string{base, p}, "/")
  171. if propSchema.Ref.String() != "" {
  172. if apiObject == trimObjectName(propSchema.Ref.String()) {
  173. return []behaviors.Behavior{}
  174. }
  175. return objectBehaviors(id, &propSchema)
  176. }
  177. var b []behaviors.Behavior
  178. switch propSchema.Type[0] {
  179. case "array":
  180. b = objectBehaviors(id, propSchema.Items.Schema)
  181. case "boolean":
  182. b = []behaviors.Behavior{
  183. {
  184. ID: id,
  185. APIObject: apiObject,
  186. APIField: p,
  187. APIType: propSchema.Type[0],
  188. Description: "Boolean set to true. " + propSchema.Description,
  189. },
  190. {
  191. ID: id,
  192. APIObject: apiObject,
  193. APIField: p,
  194. APIType: propSchema.Type[0],
  195. Description: "Boolean set to false. " + propSchema.Description,
  196. },
  197. }
  198. default:
  199. b = []behaviors.Behavior{{
  200. ID: id,
  201. APIObject: apiObject,
  202. APIField: p,
  203. APIType: propSchema.Type[0],
  204. Description: propSchema.Description,
  205. }}
  206. }
  207. return b
  208. }