document_v3.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /*
  2. Copyright 2022 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 proto
  14. import (
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. openapi_v3 "github.com/google/gnostic-models/openapiv3"
  19. "gopkg.in/yaml.v3"
  20. )
  21. // Temporary parse implementation to be used until gnostic->kube-openapi conversion
  22. // is possible.
  23. func NewOpenAPIV3Data(doc *openapi_v3.Document) (Models, error) {
  24. definitions := Definitions{
  25. models: map[string]Schema{},
  26. }
  27. schemas := doc.GetComponents().GetSchemas()
  28. if schemas == nil {
  29. return &definitions, nil
  30. }
  31. // Save the list of all models first. This will allow us to
  32. // validate that we don't have any dangling reference.
  33. for _, namedSchema := range schemas.GetAdditionalProperties() {
  34. definitions.models[namedSchema.GetName()] = nil
  35. }
  36. // Now, parse each model. We can validate that references exists.
  37. for _, namedSchema := range schemas.GetAdditionalProperties() {
  38. path := NewPath(namedSchema.GetName())
  39. val := namedSchema.GetValue()
  40. if val == nil {
  41. continue
  42. }
  43. if schema, err := definitions.ParseV3SchemaOrReference(namedSchema.GetValue(), &path); err != nil {
  44. return nil, err
  45. } else if schema != nil {
  46. // Schema may be nil if we hit incompleteness in the conversion,
  47. // but not a fatal error
  48. definitions.models[namedSchema.GetName()] = schema
  49. }
  50. }
  51. return &definitions, nil
  52. }
  53. func (d *Definitions) ParseV3SchemaReference(s *openapi_v3.Reference, path *Path) (Schema, error) {
  54. base := &BaseSchema{
  55. Description: s.Description,
  56. }
  57. if !strings.HasPrefix(s.GetXRef(), "#/components/schemas") {
  58. // Only resolve references to components/schemas. We may add support
  59. // later for other in-spec paths, but otherwise treat unrecognized
  60. // refs as arbitrary/unknown values.
  61. return &Arbitrary{
  62. BaseSchema: *base,
  63. }, nil
  64. }
  65. reference := strings.TrimPrefix(s.GetXRef(), "#/components/schemas/")
  66. if _, ok := d.models[reference]; !ok {
  67. return nil, newSchemaError(path, "unknown model in reference: %q", reference)
  68. }
  69. return &Ref{
  70. BaseSchema: BaseSchema{
  71. Description: s.Description,
  72. },
  73. reference: reference,
  74. definitions: d,
  75. }, nil
  76. }
  77. func (d *Definitions) ParseV3SchemaOrReference(s *openapi_v3.SchemaOrReference, path *Path) (Schema, error) {
  78. var schema Schema
  79. var err error
  80. switch v := s.GetOneof().(type) {
  81. case *openapi_v3.SchemaOrReference_Reference:
  82. // Any references stored in #!/components/... are bound to refer
  83. // to external documents. This API does not support such a
  84. // feature.
  85. //
  86. // In the weird case that this is a reference to a schema that is
  87. // not external, we attempt to parse anyway
  88. schema, err = d.ParseV3SchemaReference(v.Reference, path)
  89. case *openapi_v3.SchemaOrReference_Schema:
  90. schema, err = d.ParseSchemaV3(v.Schema, path)
  91. default:
  92. panic("unexpected type")
  93. }
  94. return schema, err
  95. }
  96. // ParseSchema creates a walkable Schema from an openapi v3 schema. While
  97. // this function is public, it doesn't leak through the interface.
  98. func (d *Definitions) ParseSchemaV3(s *openapi_v3.Schema, path *Path) (Schema, error) {
  99. switch s.GetType() {
  100. case object:
  101. for _, extension := range s.GetSpecificationExtension() {
  102. if extension.Name == "x-kubernetes-group-version-kind" {
  103. // Objects with x-kubernetes-group-version-kind are always top
  104. // level types.
  105. return d.parseV3Kind(s, path)
  106. }
  107. }
  108. if len(s.GetProperties().GetAdditionalProperties()) > 0 {
  109. return d.parseV3Kind(s, path)
  110. }
  111. return d.parseV3Map(s, path)
  112. case array:
  113. return d.parseV3Array(s, path)
  114. case String, Number, Integer, Boolean:
  115. return d.parseV3Primitive(s, path)
  116. default:
  117. return d.parseV3Arbitrary(s, path)
  118. }
  119. }
  120. func (d *Definitions) parseV3Kind(s *openapi_v3.Schema, path *Path) (Schema, error) {
  121. if s.GetType() != object {
  122. return nil, newSchemaError(path, "invalid object type")
  123. } else if s.GetProperties() == nil {
  124. return nil, newSchemaError(path, "object doesn't have properties")
  125. }
  126. fields := map[string]Schema{}
  127. fieldOrder := []string{}
  128. for _, namedSchema := range s.GetProperties().GetAdditionalProperties() {
  129. var err error
  130. name := namedSchema.GetName()
  131. path := path.FieldPath(name)
  132. fields[name], err = d.ParseV3SchemaOrReference(namedSchema.GetValue(), &path)
  133. if err != nil {
  134. return nil, err
  135. }
  136. fieldOrder = append(fieldOrder, name)
  137. }
  138. base, err := d.parseV3BaseSchema(s, path)
  139. if err != nil {
  140. return nil, err
  141. }
  142. return &Kind{
  143. BaseSchema: *base,
  144. RequiredFields: s.GetRequired(),
  145. Fields: fields,
  146. FieldOrder: fieldOrder,
  147. }, nil
  148. }
  149. func (d *Definitions) parseV3Arbitrary(s *openapi_v3.Schema, path *Path) (Schema, error) {
  150. base, err := d.parseV3BaseSchema(s, path)
  151. if err != nil {
  152. return nil, err
  153. }
  154. return &Arbitrary{
  155. BaseSchema: *base,
  156. }, nil
  157. }
  158. func (d *Definitions) parseV3Primitive(s *openapi_v3.Schema, path *Path) (Schema, error) {
  159. switch s.GetType() {
  160. case String: // do nothing
  161. case Number: // do nothing
  162. case Integer: // do nothing
  163. case Boolean: // do nothing
  164. default:
  165. // Unsupported primitive type. Treat as arbitrary type
  166. return d.parseV3Arbitrary(s, path)
  167. }
  168. base, err := d.parseV3BaseSchema(s, path)
  169. if err != nil {
  170. return nil, err
  171. }
  172. return &Primitive{
  173. BaseSchema: *base,
  174. Type: s.GetType(),
  175. Format: s.GetFormat(),
  176. }, nil
  177. }
  178. func (d *Definitions) parseV3Array(s *openapi_v3.Schema, path *Path) (Schema, error) {
  179. if s.GetType() != array {
  180. return nil, newSchemaError(path, `array should have type "array"`)
  181. } else if len(s.GetItems().GetSchemaOrReference()) != 1 {
  182. // This array can have multiple types in it (or no types at all)
  183. // This is not supported by this conversion.
  184. // Just return an arbitrary type
  185. return d.parseV3Arbitrary(s, path)
  186. }
  187. sub, err := d.ParseV3SchemaOrReference(s.GetItems().GetSchemaOrReference()[0], path)
  188. if err != nil {
  189. return nil, err
  190. }
  191. base, err := d.parseV3BaseSchema(s, path)
  192. if err != nil {
  193. return nil, err
  194. }
  195. return &Array{
  196. BaseSchema: *base,
  197. SubType: sub,
  198. }, nil
  199. }
  200. // We believe the schema is a map, verify and return a new schema
  201. func (d *Definitions) parseV3Map(s *openapi_v3.Schema, path *Path) (Schema, error) {
  202. if s.GetType() != object {
  203. return nil, newSchemaError(path, "invalid object type")
  204. }
  205. var sub Schema
  206. switch p := s.GetAdditionalProperties().GetOneof().(type) {
  207. case *openapi_v3.AdditionalPropertiesItem_Boolean:
  208. // What does this boolean even mean?
  209. base, err := d.parseV3BaseSchema(s, path)
  210. if err != nil {
  211. return nil, err
  212. }
  213. sub = &Arbitrary{
  214. BaseSchema: *base,
  215. }
  216. case *openapi_v3.AdditionalPropertiesItem_SchemaOrReference:
  217. if schema, err := d.ParseV3SchemaOrReference(p.SchemaOrReference, path); err != nil {
  218. return nil, err
  219. } else {
  220. sub = schema
  221. }
  222. case nil:
  223. // no subtype?
  224. sub = &Arbitrary{}
  225. default:
  226. panic("unrecognized type " + reflect.TypeOf(p).Name())
  227. }
  228. base, err := d.parseV3BaseSchema(s, path)
  229. if err != nil {
  230. return nil, err
  231. }
  232. return &Map{
  233. BaseSchema: *base,
  234. SubType: sub,
  235. }, nil
  236. }
  237. func parseV3Interface(def *yaml.Node) (interface{}, error) {
  238. if def == nil {
  239. return nil, nil
  240. }
  241. var i interface{}
  242. if err := def.Decode(&i); err != nil {
  243. return nil, err
  244. }
  245. return i, nil
  246. }
  247. func (d *Definitions) parseV3BaseSchema(s *openapi_v3.Schema, path *Path) (*BaseSchema, error) {
  248. if s == nil {
  249. return nil, fmt.Errorf("cannot initialize BaseSchema from nil")
  250. }
  251. def, err := parseV3Interface(s.GetDefault().ToRawInfo())
  252. if err != nil {
  253. return nil, err
  254. }
  255. return &BaseSchema{
  256. Description: s.GetDescription(),
  257. Default: def,
  258. Extensions: SpecificationExtensionToMap(s.GetSpecificationExtension()),
  259. Path: *path,
  260. }, nil
  261. }
  262. func SpecificationExtensionToMap(e []*openapi_v3.NamedAny) map[string]interface{} {
  263. values := map[string]interface{}{}
  264. for _, na := range e {
  265. if na.GetName() == "" || na.GetValue() == nil {
  266. continue
  267. }
  268. if na.GetValue().GetYaml() == "" {
  269. continue
  270. }
  271. var value interface{}
  272. err := yaml.Unmarshal([]byte(na.GetValue().GetYaml()), &value)
  273. if err != nil {
  274. continue
  275. }
  276. values[na.GetName()] = value
  277. }
  278. return values
  279. }