smd.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. "strings"
  19. "k8s.io/kube-openapi/pkg/util/proto"
  20. "sigs.k8s.io/structured-merge-diff/schema"
  21. )
  22. // ToSchema converts openapi definitions into a schema suitable for structured
  23. // merge (i.e. kubectl apply v2).
  24. func ToSchema(models proto.Models) (*schema.Schema, error) {
  25. c := convert{
  26. input: models,
  27. output: &schema.Schema{},
  28. }
  29. if err := c.convertAll(); err != nil {
  30. return nil, err
  31. }
  32. return c.output, nil
  33. }
  34. type convert struct {
  35. input proto.Models
  36. output *schema.Schema
  37. currentName string
  38. current *schema.Atom
  39. errorMessages []string
  40. }
  41. func (c *convert) push(name string, a *schema.Atom) *convert {
  42. return &convert{
  43. input: c.input,
  44. output: c.output,
  45. currentName: name,
  46. current: a,
  47. }
  48. }
  49. func (c *convert) top() *schema.Atom { return c.current }
  50. func (c *convert) pop(c2 *convert) {
  51. c.errorMessages = append(c.errorMessages, c2.errorMessages...)
  52. }
  53. func (c *convert) convertAll() error {
  54. for _, name := range c.input.ListModels() {
  55. model := c.input.LookupModel(name)
  56. c.insertTypeDef(name, model)
  57. }
  58. if len(c.errorMessages) > 0 {
  59. return errors.New(strings.Join(c.errorMessages, "\n"))
  60. }
  61. return nil
  62. }
  63. func (c *convert) reportError(format string, args ...interface{}) {
  64. c.errorMessages = append(c.errorMessages,
  65. c.currentName+": "+fmt.Sprintf(format, args...),
  66. )
  67. }
  68. func (c *convert) insertTypeDef(name string, model proto.Schema) {
  69. def := schema.TypeDef{
  70. Name: name,
  71. }
  72. c2 := c.push(name, &def.Atom)
  73. model.Accept(c2)
  74. c.pop(c2)
  75. if def.Atom == (schema.Atom{}) {
  76. // This could happen if there were a top-level reference.
  77. return
  78. }
  79. c.output.Types = append(c.output.Types, def)
  80. }
  81. func (c *convert) makeRef(model proto.Schema) schema.TypeRef {
  82. var tr schema.TypeRef
  83. if r, ok := model.(*proto.Ref); ok {
  84. if r.Reference() == "io.k8s.apimachinery.pkg.runtime.RawExtension" {
  85. return schema.TypeRef{
  86. Inlined: schema.Atom{
  87. Untyped: &schema.Untyped{},
  88. },
  89. }
  90. }
  91. // reference a named type
  92. _, n := path.Split(r.Reference())
  93. tr.NamedType = &n
  94. } else {
  95. // compute the type inline
  96. c2 := c.push("inlined in "+c.currentName, &tr.Inlined)
  97. model.Accept(c2)
  98. c.pop(c2)
  99. if tr == (schema.TypeRef{}) {
  100. // emit warning?
  101. tr.Inlined.Untyped = &schema.Untyped{}
  102. }
  103. }
  104. return tr
  105. }
  106. func (c *convert) VisitKind(k *proto.Kind) {
  107. a := c.top()
  108. a.Struct = &schema.Struct{}
  109. for _, name := range k.FieldOrder {
  110. member := k.Fields[name]
  111. tr := c.makeRef(member)
  112. a.Struct.Fields = append(a.Struct.Fields, schema.StructField{
  113. Name: name,
  114. Type: tr,
  115. })
  116. }
  117. // TODO: Get element relationship when we start adding it to the spec.
  118. }
  119. func toStringSlice(o interface{}) (out []string, ok bool) {
  120. switch t := o.(type) {
  121. case []interface{}:
  122. for _, v := range t {
  123. switch vt := v.(type) {
  124. case string:
  125. out = append(out, vt)
  126. }
  127. }
  128. return out, true
  129. }
  130. return nil, false
  131. }
  132. func (c *convert) VisitArray(a *proto.Array) {
  133. atom := c.top()
  134. atom.List = &schema.List{
  135. ElementRelationship: schema.Atomic,
  136. }
  137. l := atom.List
  138. l.ElementType = c.makeRef(a.SubType)
  139. ext := a.GetExtensions()
  140. if val, ok := ext["x-kubernetes-list-type"]; ok {
  141. if val == "atomic" {
  142. l.ElementRelationship = schema.Atomic
  143. } else if val == "set" {
  144. l.ElementRelationship = schema.Associative
  145. } else if val == "map" {
  146. l.ElementRelationship = schema.Associative
  147. if keys, ok := ext["x-kubernetes-list-map-keys"]; ok {
  148. if keyNames, ok := toStringSlice(keys); ok {
  149. l.Keys = keyNames
  150. } else {
  151. c.reportError("uninterpreted map keys: %#v", keys)
  152. }
  153. } else {
  154. c.reportError("missing map keys")
  155. }
  156. } else {
  157. c.reportError("unknown list type %v", val)
  158. l.ElementRelationship = schema.Atomic
  159. }
  160. } else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
  161. if val == "merge" || val == "merge,retainKeys" {
  162. l.ElementRelationship = schema.Associative
  163. if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
  164. if keyName, ok := key.(string); ok {
  165. l.Keys = []string{keyName}
  166. } else {
  167. c.reportError("uninterpreted merge key: %#v", key)
  168. }
  169. } else {
  170. // It's not an error for this to be absent, it
  171. // means it's a set.
  172. }
  173. } else if val == "retainKeys" {
  174. } else {
  175. c.reportError("unknown patch strategy %v", val)
  176. l.ElementRelationship = schema.Atomic
  177. }
  178. }
  179. }
  180. func (c *convert) VisitMap(m *proto.Map) {
  181. a := c.top()
  182. a.Map = &schema.Map{}
  183. a.Map.ElementType = c.makeRef(m.SubType)
  184. // TODO: Get element relationship when we start putting it into the
  185. // spec.
  186. }
  187. func (c *convert) VisitPrimitive(p *proto.Primitive) {
  188. a := c.top()
  189. ptr := func(s schema.Scalar) *schema.Scalar { return &s }
  190. switch p.Type {
  191. case proto.Integer:
  192. a.Scalar = ptr(schema.Numeric)
  193. case proto.Number:
  194. a.Scalar = ptr(schema.Numeric)
  195. case proto.String:
  196. switch p.Format {
  197. case "":
  198. a.Scalar = ptr(schema.String)
  199. case "byte":
  200. // byte really means []byte and is encoded as a string.
  201. a.Scalar = ptr(schema.String)
  202. case "int-or-string":
  203. a.Untyped = &schema.Untyped{}
  204. case "date-time":
  205. a.Untyped = &schema.Untyped{}
  206. default:
  207. a.Untyped = &schema.Untyped{}
  208. }
  209. case proto.Boolean:
  210. a.Scalar = ptr(schema.Boolean)
  211. default:
  212. a.Untyped = &schema.Untyped{}
  213. }
  214. }
  215. func (c *convert) VisitArbitrary(a *proto.Arbitrary) {
  216. c.top().Untyped = &schema.Untyped{}
  217. }
  218. func (c *convert) VisitReference(proto.Reference) {
  219. // Do nothing, we handle references specially
  220. }