smd.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. Copyright 2017 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 schemaconv
  14. import (
  15. "errors"
  16. "fmt"
  17. "path"
  18. "sort"
  19. "strings"
  20. "k8s.io/kube-openapi/pkg/util/proto"
  21. "sigs.k8s.io/structured-merge-diff/v4/schema"
  22. )
  23. const (
  24. quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
  25. )
  26. // ToSchema converts openapi definitions into a schema suitable for structured
  27. // merge (i.e. kubectl apply v2).
  28. func ToSchema(models proto.Models) (*schema.Schema, error) {
  29. return ToSchemaWithPreserveUnknownFields(models, false)
  30. }
  31. // ToSchemaWithPreserveUnknownFields converts openapi definitions into a schema suitable for structured
  32. // merge (i.e. kubectl apply v2), it will preserve unknown fields if specified.
  33. func ToSchemaWithPreserveUnknownFields(models proto.Models, preserveUnknownFields bool) (*schema.Schema, error) {
  34. c := convert{
  35. input: models,
  36. preserveUnknownFields: preserveUnknownFields,
  37. output: &schema.Schema{},
  38. }
  39. if err := c.convertAll(); err != nil {
  40. return nil, err
  41. }
  42. c.addCommonTypes()
  43. return c.output, nil
  44. }
  45. type convert struct {
  46. input proto.Models
  47. preserveUnknownFields bool
  48. output *schema.Schema
  49. currentName string
  50. current *schema.Atom
  51. errorMessages []string
  52. }
  53. func (c *convert) push(name string, a *schema.Atom) *convert {
  54. return &convert{
  55. input: c.input,
  56. preserveUnknownFields: c.preserveUnknownFields,
  57. output: c.output,
  58. currentName: name,
  59. current: a,
  60. }
  61. }
  62. func (c *convert) top() *schema.Atom { return c.current }
  63. func (c *convert) pop(c2 *convert) {
  64. c.errorMessages = append(c.errorMessages, c2.errorMessages...)
  65. }
  66. func (c *convert) convertAll() error {
  67. for _, name := range c.input.ListModels() {
  68. model := c.input.LookupModel(name)
  69. c.insertTypeDef(name, model)
  70. }
  71. if len(c.errorMessages) > 0 {
  72. return errors.New(strings.Join(c.errorMessages, "\n"))
  73. }
  74. return nil
  75. }
  76. func (c *convert) reportError(format string, args ...interface{}) {
  77. c.errorMessages = append(c.errorMessages,
  78. c.currentName+": "+fmt.Sprintf(format, args...),
  79. )
  80. }
  81. func (c *convert) insertTypeDef(name string, model proto.Schema) {
  82. def := schema.TypeDef{
  83. Name: name,
  84. }
  85. c2 := c.push(name, &def.Atom)
  86. model.Accept(c2)
  87. c.pop(c2)
  88. if def.Atom == (schema.Atom{}) {
  89. // This could happen if there were a top-level reference.
  90. return
  91. }
  92. c.output.Types = append(c.output.Types, def)
  93. }
  94. func (c *convert) addCommonTypes() {
  95. c.output.Types = append(c.output.Types, untypedDef)
  96. c.output.Types = append(c.output.Types, deducedDef)
  97. }
  98. var untypedName string = "__untyped_atomic_"
  99. var untypedDef schema.TypeDef = schema.TypeDef{
  100. Name: untypedName,
  101. Atom: schema.Atom{
  102. Scalar: ptr(schema.Scalar("untyped")),
  103. List: &schema.List{
  104. ElementType: schema.TypeRef{
  105. NamedType: &untypedName,
  106. },
  107. ElementRelationship: schema.Atomic,
  108. },
  109. Map: &schema.Map{
  110. ElementType: schema.TypeRef{
  111. NamedType: &untypedName,
  112. },
  113. ElementRelationship: schema.Atomic,
  114. },
  115. },
  116. }
  117. var deducedName string = "__untyped_deduced_"
  118. var deducedDef schema.TypeDef = schema.TypeDef{
  119. Name: deducedName,
  120. Atom: schema.Atom{
  121. Scalar: ptr(schema.Scalar("untyped")),
  122. List: &schema.List{
  123. ElementType: schema.TypeRef{
  124. NamedType: &untypedName,
  125. },
  126. ElementRelationship: schema.Atomic,
  127. },
  128. Map: &schema.Map{
  129. ElementType: schema.TypeRef{
  130. NamedType: &deducedName,
  131. },
  132. ElementRelationship: schema.Separable,
  133. },
  134. },
  135. }
  136. func (c *convert) makeRef(model proto.Schema, preserveUnknownFields bool) schema.TypeRef {
  137. var tr schema.TypeRef
  138. if r, ok := model.(*proto.Ref); ok {
  139. if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
  140. return schema.TypeRef{
  141. NamedType: &untypedName,
  142. }
  143. }
  144. // reference a named type
  145. _, n := path.Split(r.Reference())
  146. tr.NamedType = &n
  147. ext := model.GetExtensions()
  148. if val, ok := ext["x-kubernetes-map-type"]; ok {
  149. switch val {
  150. case "atomic":
  151. relationship := schema.Atomic
  152. tr.ElementRelationship = &relationship
  153. case "granular":
  154. relationship := schema.Separable
  155. tr.ElementRelationship = &relationship
  156. default:
  157. c.reportError("unknown map type %v", val)
  158. }
  159. }
  160. } else {
  161. // compute the type inline
  162. c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
  163. c2.preserveUnknownFields = preserveUnknownFields
  164. model.Accept(c2)
  165. c.pop(c2)
  166. if tr == (schema.TypeRef{}) {
  167. // emit warning?
  168. tr.NamedType = &untypedName
  169. }
  170. }
  171. return tr
  172. }
  173. func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) {
  174. schemaUnions := []schema.Union{}
  175. if iunions, ok := extensions["x-kubernetes-unions"]; ok {
  176. unions, ok := iunions.([]interface{})
  177. if !ok {
  178. return nil, fmt.Errorf(`"x-kubernetes-unions" should be a list, got %#v`, unions)
  179. }
  180. for _, iunion := range unions {
  181. union, ok := iunion.(map[interface{}]interface{})
  182. if !ok {
  183. return nil, fmt.Errorf(`"x-kubernetes-unions" items should be a map of string to unions, got %#v`, iunion)
  184. }
  185. unionMap := map[string]interface{}{}
  186. for k, v := range union {
  187. key, ok := k.(string)
  188. if !ok {
  189. return nil, fmt.Errorf(`"x-kubernetes-unions" has non-string key: %#v`, k)
  190. }
  191. unionMap[key] = v
  192. }
  193. schemaUnion, err := makeUnion(unionMap)
  194. if err != nil {
  195. return nil, err
  196. }
  197. schemaUnions = append(schemaUnions, schemaUnion)
  198. }
  199. }
  200. // Make sure we have no overlap between unions
  201. fs := map[string]struct{}{}
  202. for _, u := range schemaUnions {
  203. if u.Discriminator != nil {
  204. if _, ok := fs[*u.Discriminator]; ok {
  205. return nil, fmt.Errorf("%v field appears multiple times in unions", *u.Discriminator)
  206. }
  207. fs[*u.Discriminator] = struct{}{}
  208. }
  209. for _, f := range u.Fields {
  210. if _, ok := fs[f.FieldName]; ok {
  211. return nil, fmt.Errorf("%v field appears multiple times in unions", f.FieldName)
  212. }
  213. fs[f.FieldName] = struct{}{}
  214. }
  215. }
  216. return schemaUnions, nil
  217. }
  218. func makeUnion(extensions map[string]interface{}) (schema.Union, error) {
  219. union := schema.Union{
  220. Fields: []schema.UnionField{},
  221. }
  222. if idiscriminator, ok := extensions["discriminator"]; ok {
  223. discriminator, ok := idiscriminator.(string)
  224. if !ok {
  225. return schema.Union{}, fmt.Errorf(`"discriminator" must be a string, got: %#v`, idiscriminator)
  226. }
  227. union.Discriminator = &discriminator
  228. }
  229. if ifields, ok := extensions["fields-to-discriminateBy"]; ok {
  230. fields, ok := ifields.(map[interface{}]interface{})
  231. if !ok {
  232. return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy" must be a map[string]string, got: %#v`, ifields)
  233. }
  234. // Needs sorted keys by field.
  235. keys := []string{}
  236. for ifield := range fields {
  237. field, ok := ifield.(string)
  238. if !ok {
  239. return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy": field must be a string, got: %#v`, ifield)
  240. }
  241. keys = append(keys, field)
  242. }
  243. sort.Strings(keys)
  244. reverseMap := map[string]struct{}{}
  245. for _, field := range keys {
  246. value := fields[field]
  247. discriminated, ok := value.(string)
  248. if !ok {
  249. return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy"/%v: value must be a string, got: %#v`, field, value)
  250. }
  251. union.Fields = append(union.Fields, schema.UnionField{
  252. FieldName: field,
  253. DiscriminatorValue: discriminated,
  254. })
  255. // Check that we don't have the same discriminateBy multiple times.
  256. if _, ok := reverseMap[discriminated]; ok {
  257. return schema.Union{}, fmt.Errorf("Multiple fields have the same discriminated name: %v", discriminated)
  258. }
  259. reverseMap[discriminated] = struct{}{}
  260. }
  261. }
  262. if union.Discriminator != nil && len(union.Fields) == 0 {
  263. return schema.Union{}, fmt.Errorf("discriminator set to %v, but no fields in union", *union.Discriminator)
  264. }
  265. return union, nil
  266. }
  267. func (c *convert) VisitKind(k *proto.Kind) {
  268. preserveUnknownFields := c.preserveUnknownFields
  269. if p, ok := k.GetExtensions()["x-kubernetes-preserve-unknown-fields"]; ok && p == true {
  270. preserveUnknownFields = true
  271. }
  272. a := c.top()
  273. a.Map = &schema.Map{}
  274. for _, name := range k.FieldOrder {
  275. member := k.Fields[name]
  276. tr := c.makeRef(member, preserveUnknownFields)
  277. a.Map.Fields = append(a.Map.Fields, schema.StructField{
  278. Name: name,
  279. Type: tr,
  280. Default: member.GetDefault(),
  281. })
  282. }
  283. unions, err := makeUnions(k.GetExtensions())
  284. if err != nil {
  285. c.reportError(err.Error())
  286. return
  287. }
  288. // TODO: We should check that the fields and discriminator
  289. // specified in the union are actual fields in the struct.
  290. a.Map.Unions = unions
  291. if preserveUnknownFields {
  292. a.Map.ElementType = schema.TypeRef{
  293. NamedType: &deducedName,
  294. }
  295. }
  296. ext := k.GetExtensions()
  297. if val, ok := ext["x-kubernetes-map-type"]; ok {
  298. switch val {
  299. case "atomic":
  300. a.Map.ElementRelationship = schema.Atomic
  301. case "granular":
  302. a.Map.ElementRelationship = schema.Separable
  303. default:
  304. c.reportError("unknown map type %v", val)
  305. }
  306. }
  307. }
  308. func toStringSlice(o interface{}) (out []string, ok bool) {
  309. switch t := o.(type) {
  310. case []interface{}:
  311. for _, v := range t {
  312. switch vt := v.(type) {
  313. case string:
  314. out = append(out, vt)
  315. }
  316. }
  317. return out, true
  318. }
  319. return nil, false
  320. }
  321. func (c *convert) VisitArray(a *proto.Array) {
  322. atom := c.top()
  323. atom.List = &schema.List{
  324. ElementRelationship: schema.Atomic,
  325. }
  326. l := atom.List
  327. l.ElementType = c.makeRef(a.SubType, c.preserveUnknownFields)
  328. ext := a.GetExtensions()
  329. if val, ok := ext["x-kubernetes-list-type"]; ok {
  330. if val == "atomic" {
  331. l.ElementRelationship = schema.Atomic
  332. } else if val == "set" {
  333. l.ElementRelationship = schema.Associative
  334. } else if val == "map" {
  335. l.ElementRelationship = schema.Associative
  336. if keys, ok := ext["x-kubernetes-list-map-keys"]; ok {
  337. if keyNames, ok := toStringSlice(keys); ok {
  338. l.Keys = keyNames
  339. } else {
  340. c.reportError("uninterpreted map keys: %#v", keys)
  341. }
  342. } else {
  343. c.reportError("missing map keys")
  344. }
  345. } else {
  346. c.reportError("unknown list type %v", val)
  347. l.ElementRelationship = schema.Atomic
  348. }
  349. } else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
  350. if val == "merge" || val == "merge,retainKeys" {
  351. l.ElementRelationship = schema.Associative
  352. if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
  353. if keyName, ok := key.(string); ok {
  354. l.Keys = []string{keyName}
  355. } else {
  356. c.reportError("uninterpreted merge key: %#v", key)
  357. }
  358. } else {
  359. // It's not an error for this to be absent, it
  360. // means it's a set.
  361. }
  362. } else if val == "retainKeys" {
  363. } else {
  364. c.reportError("unknown patch strategy %v", val)
  365. l.ElementRelationship = schema.Atomic
  366. }
  367. }
  368. }
  369. func (c *convert) VisitMap(m *proto.Map) {
  370. a := c.top()
  371. a.Map = &schema.Map{}
  372. a.Map.ElementType = c.makeRef(m.SubType, c.preserveUnknownFields)
  373. ext := m.GetExtensions()
  374. if val, ok := ext["x-kubernetes-map-type"]; ok {
  375. switch val {
  376. case "atomic":
  377. a.Map.ElementRelationship = schema.Atomic
  378. case "granular":
  379. a.Map.ElementRelationship = schema.Separable
  380. default:
  381. c.reportError("unknown map type %v", val)
  382. }
  383. }
  384. }
  385. func ptr(s schema.Scalar) *schema.Scalar { return &s }
  386. func (c *convert) VisitPrimitive(p *proto.Primitive) {
  387. a := c.top()
  388. if c.currentName == quantityResource {
  389. a.Scalar = ptr(schema.Scalar("untyped"))
  390. } else {
  391. switch p.Type {
  392. case proto.Integer:
  393. a.Scalar = ptr(schema.Numeric)
  394. case proto.Number:
  395. a.Scalar = ptr(schema.Numeric)
  396. case proto.String:
  397. switch p.Format {
  398. case "":
  399. a.Scalar = ptr(schema.String)
  400. case "byte":
  401. // byte really means []byte and is encoded as a string.
  402. a.Scalar = ptr(schema.String)
  403. case "int-or-string":
  404. a.Scalar = ptr(schema.Scalar("untyped"))
  405. case "date-time":
  406. a.Scalar = ptr(schema.Scalar("untyped"))
  407. default:
  408. a.Scalar = ptr(schema.Scalar("untyped"))
  409. }
  410. case proto.Boolean:
  411. a.Scalar = ptr(schema.Boolean)
  412. default:
  413. a.Scalar = ptr(schema.Scalar("untyped"))
  414. }
  415. }
  416. }
  417. func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
  418. *c.top() = deducedDef.Atom
  419. }
  420. func (c *convert) VisitReference(proto.Reference) {
  421. // Do nothing, we handle references specially
  422. }