aggregator.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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 aggregator
  14. import (
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. "github.com/go-openapi/spec"
  19. "k8s.io/kube-openapi/pkg/util"
  20. )
  21. // usedDefinitionForSpec returns a map with all used definitions in the provided spec as keys and true as values.
  22. func usedDefinitionForSpec(root *spec.Swagger) map[string]bool {
  23. usedDefinitions := map[string]bool{}
  24. walkOnAllReferences(func(ref *spec.Ref) {
  25. if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) {
  26. usedDefinitions[refStr[len(definitionPrefix):]] = true
  27. }
  28. }, root)
  29. return usedDefinitions
  30. }
  31. // FilterSpecByPaths removes unnecessary paths and definitions used by those paths.
  32. // i.e. if a Path removed by this function, all definitions used by it and not used
  33. // anywhere else will also be removed.
  34. func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
  35. *sp = *FilterSpecByPathsWithoutSideEffects(sp, keepPathPrefixes)
  36. }
  37. // FilterSpecByPathsWithoutSideEffects removes unnecessary paths and definitions used by those paths.
  38. // i.e. if a Path removed by this function, all definitions used by it and not used
  39. // anywhere else will also be removed.
  40. // It does not modify the input, but the output shares data structures with the input.
  41. func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []string) *spec.Swagger {
  42. if sp.Paths == nil {
  43. return sp
  44. }
  45. // Walk all references to find all used definitions. This function
  46. // want to only deal with unused definitions resulted from filtering paths.
  47. // Thus a definition will be removed only if it has been used before but
  48. // it is unused because of a path prune.
  49. initialUsedDefinitions := usedDefinitionForSpec(sp)
  50. // First remove unwanted paths
  51. prefixes := util.NewTrie(keepPathPrefixes)
  52. ret := *sp
  53. ret.Paths = &spec.Paths{
  54. VendorExtensible: sp.Paths.VendorExtensible,
  55. Paths: map[string]spec.PathItem{},
  56. }
  57. for path, pathItem := range sp.Paths.Paths {
  58. if !prefixes.HasPrefix(path) {
  59. continue
  60. }
  61. ret.Paths.Paths[path] = pathItem
  62. }
  63. // Walk all references to find all definition references.
  64. usedDefinitions := usedDefinitionForSpec(&ret)
  65. // Remove unused definitions
  66. ret.Definitions = spec.Definitions{}
  67. for k, v := range sp.Definitions {
  68. if usedDefinitions[k] || !initialUsedDefinitions[k] {
  69. ret.Definitions[k] = v
  70. }
  71. }
  72. return &ret
  73. }
  74. type rename struct {
  75. from, to string
  76. }
  77. // renameDefinition renames references, without mutating the input.
  78. // The output might share data structures with the input.
  79. func renameDefinition(s *spec.Swagger, renames map[string]string) *spec.Swagger {
  80. refRenames := make(map[string]string, len(renames))
  81. foundOne := false
  82. for k, v := range renames {
  83. refRenames[definitionPrefix+k] = definitionPrefix + v
  84. if _, ok := s.Definitions[k]; ok {
  85. foundOne = true
  86. }
  87. }
  88. if !foundOne {
  89. return s
  90. }
  91. ret := &spec.Swagger{}
  92. *ret = *s
  93. ret = replaceReferences(func(ref *spec.Ref) *spec.Ref {
  94. refName := ref.String()
  95. if newRef, found := refRenames[refName]; found {
  96. ret := spec.MustCreateRef(newRef)
  97. return &ret
  98. }
  99. return ref
  100. }, ret)
  101. renamedDefinitions := make(spec.Definitions, len(ret.Definitions))
  102. for k, v := range ret.Definitions {
  103. if newRef, found := renames[k]; found {
  104. k = newRef
  105. }
  106. renamedDefinitions[k] = v
  107. }
  108. ret.Definitions = renamedDefinitions
  109. return ret
  110. }
  111. // MergeSpecsIgnorePathConflict is the same as MergeSpecs except it will ignore any path
  112. // conflicts by keeping the paths of destination. It will rename definition conflicts.
  113. // The source is not mutated.
  114. func MergeSpecsIgnorePathConflict(dest, source *spec.Swagger) error {
  115. return mergeSpecs(dest, source, true, true)
  116. }
  117. // MergeSpecsFailOnDefinitionConflict is differ from MergeSpecs as it fails if there is
  118. // a definition conflict.
  119. // The source is not mutated.
  120. func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error {
  121. return mergeSpecs(dest, source, false, false)
  122. }
  123. // MergeSpecs copies paths and definitions from source to dest, rename definitions if needed.
  124. // dest will be mutated, and source will not be changed. It will fail on path conflicts.
  125. // The source is not mutated.
  126. func MergeSpecs(dest, source *spec.Swagger) error {
  127. return mergeSpecs(dest, source, true, false)
  128. }
  129. // mergeSpecs merged source into dest while resolving conflicts.
  130. // The source is not mutated.
  131. func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConflicts bool) (err error) {
  132. // Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
  133. if source.Paths == nil {
  134. // When a source spec does not have any path, that means none of the definitions
  135. // are used thus we should not do anything
  136. return nil
  137. }
  138. if dest.Paths == nil {
  139. dest.Paths = &spec.Paths{}
  140. }
  141. if ignorePathConflicts {
  142. keepPaths := []string{}
  143. hasConflictingPath := false
  144. for k := range source.Paths.Paths {
  145. if _, found := dest.Paths.Paths[k]; !found {
  146. keepPaths = append(keepPaths, k)
  147. } else {
  148. hasConflictingPath = true
  149. }
  150. }
  151. if len(keepPaths) == 0 {
  152. // There is nothing to merge. All paths are conflicting.
  153. return nil
  154. }
  155. if hasConflictingPath {
  156. source = FilterSpecByPathsWithoutSideEffects(source, keepPaths)
  157. }
  158. }
  159. // Check for model conflicts
  160. conflicts := false
  161. for k, v := range source.Definitions {
  162. v2, found := dest.Definitions[k]
  163. if found && !reflect.DeepEqual(v, v2) {
  164. if !renameModelConflicts {
  165. return fmt.Errorf("model name conflict in merging OpenAPI spec: %s", k)
  166. }
  167. conflicts = true
  168. break
  169. }
  170. }
  171. if conflicts {
  172. usedNames := map[string]bool{}
  173. for k := range dest.Definitions {
  174. usedNames[k] = true
  175. }
  176. renames := map[string]string{}
  177. OUTERLOOP:
  178. for k, v := range source.Definitions {
  179. if usedNames[k] {
  180. v2, found := dest.Definitions[k]
  181. // Reuse model if they are exactly the same.
  182. if found && reflect.DeepEqual(v, v2) {
  183. continue
  184. }
  185. // Reuse previously renamed model if one exists
  186. var newName string
  187. i := 1
  188. for found {
  189. i++
  190. newName = fmt.Sprintf("%s_v%d", k, i)
  191. v2, found = dest.Definitions[newName]
  192. if found && reflect.DeepEqual(v, v2) {
  193. renames[k] = newName
  194. continue OUTERLOOP
  195. }
  196. }
  197. _, foundInSource := source.Definitions[newName]
  198. for usedNames[newName] || foundInSource {
  199. i++
  200. newName = fmt.Sprintf("%s_v%d", k, i)
  201. _, foundInSource = source.Definitions[newName]
  202. }
  203. renames[k] = newName
  204. usedNames[newName] = true
  205. }
  206. }
  207. source = renameDefinition(source, renames)
  208. }
  209. for k, v := range source.Definitions {
  210. if _, found := dest.Definitions[k]; !found {
  211. if dest.Definitions == nil {
  212. dest.Definitions = spec.Definitions{}
  213. }
  214. dest.Definitions[k] = v
  215. }
  216. }
  217. // Check for path conflicts
  218. for k, v := range source.Paths.Paths {
  219. if _, found := dest.Paths.Paths[k]; found {
  220. return fmt.Errorf("unable to merge: duplicated path %s", k)
  221. }
  222. // PathItem may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
  223. if dest.Paths.Paths == nil {
  224. dest.Paths.Paths = map[string]spec.PathItem{}
  225. }
  226. dest.Paths.Paths[k] = v
  227. }
  228. return nil
  229. }