smd.go 11 KB

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