k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/aggregator/aggregator.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package aggregator
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"sort"
    23  	"strings"
    24  
    25  	"k8s.io/kube-openapi/pkg/schemamutation"
    26  	"k8s.io/kube-openapi/pkg/util"
    27  	"k8s.io/kube-openapi/pkg/validation/spec"
    28  )
    29  
    30  const gvkKey = "x-kubernetes-group-version-kind"
    31  
    32  // usedDefinitionForSpec returns a map with all used definitions in the provided spec as keys and true as values.
    33  func usedDefinitionForSpec(root *spec.Swagger) map[string]bool {
    34  	usedDefinitions := map[string]bool{}
    35  	walkOnAllReferences(func(ref *spec.Ref) {
    36  		if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) {
    37  			usedDefinitions[refStr[len(definitionPrefix):]] = true
    38  		}
    39  	}, root)
    40  	return usedDefinitions
    41  }
    42  
    43  // FilterSpecByPaths removes unnecessary paths and definitions used by those paths.
    44  // i.e. if a Path removed by this function, all definitions used by it and not used
    45  // anywhere else will also be removed.
    46  func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) {
    47  	*sp = *FilterSpecByPathsWithoutSideEffects(sp, keepPathPrefixes)
    48  }
    49  
    50  // FilterSpecByPathsWithoutSideEffects removes unnecessary paths and definitions used by those paths.
    51  // i.e. if a Path removed by this function, all definitions used by it and not used
    52  // anywhere else will also be removed.
    53  // It does not modify the input, but the output shares data structures with the input.
    54  func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []string) *spec.Swagger {
    55  	if sp.Paths == nil {
    56  		return sp
    57  	}
    58  
    59  	// Walk all references to find all used definitions. This function
    60  	// want to only deal with unused definitions resulted from filtering paths.
    61  	// Thus a definition will be removed only if it has been used before but
    62  	// it is unused because of a path prune.
    63  	initialUsedDefinitions := usedDefinitionForSpec(sp)
    64  
    65  	// First remove unwanted paths
    66  	prefixes := util.NewTrie(keepPathPrefixes)
    67  	ret := *sp
    68  	ret.Paths = &spec.Paths{
    69  		VendorExtensible: sp.Paths.VendorExtensible,
    70  		Paths:            map[string]spec.PathItem{},
    71  	}
    72  	for path, pathItem := range sp.Paths.Paths {
    73  		if !prefixes.HasPrefix(path) {
    74  			continue
    75  		}
    76  		ret.Paths.Paths[path] = pathItem
    77  	}
    78  
    79  	// Walk all references to find all definition references.
    80  	usedDefinitions := usedDefinitionForSpec(&ret)
    81  
    82  	// Remove unused definitions
    83  	ret.Definitions = spec.Definitions{}
    84  	for k, v := range sp.Definitions {
    85  		if usedDefinitions[k] || !initialUsedDefinitions[k] {
    86  			ret.Definitions[k] = v
    87  		}
    88  	}
    89  
    90  	return &ret
    91  }
    92  
    93  // renameDefinitions renames definition references, without mutating the input.
    94  // The output might share data structures with the input.
    95  func renameDefinitions(s *spec.Swagger, renames map[string]string) *spec.Swagger {
    96  	refRenames := make(map[string]string, len(renames))
    97  	foundOne := false
    98  	for k, v := range renames {
    99  		refRenames[definitionPrefix+k] = definitionPrefix + v
   100  		if _, ok := s.Definitions[k]; ok {
   101  			foundOne = true
   102  		}
   103  	}
   104  
   105  	if !foundOne {
   106  		return s
   107  	}
   108  
   109  	ret := &spec.Swagger{}
   110  	*ret = *s
   111  
   112  	ret = schemamutation.ReplaceReferences(func(ref *spec.Ref) *spec.Ref {
   113  		refName := ref.String()
   114  		if newRef, found := refRenames[refName]; found {
   115  			ret := spec.MustCreateRef(newRef)
   116  			return &ret
   117  		}
   118  		return ref
   119  	}, ret)
   120  
   121  	renamedDefinitions := make(spec.Definitions, len(ret.Definitions))
   122  	for k, v := range ret.Definitions {
   123  		if newRef, found := renames[k]; found {
   124  			k = newRef
   125  		}
   126  		renamedDefinitions[k] = v
   127  	}
   128  	ret.Definitions = renamedDefinitions
   129  
   130  	return ret
   131  }
   132  
   133  // renameParameters renames parameter references, without mutating the input.
   134  // The output might share data structures with the input.
   135  func renameParameters(s *spec.Swagger, renames map[string]string) *spec.Swagger {
   136  	refRenames := make(map[string]string, len(renames))
   137  	foundOne := false
   138  	for k, v := range renames {
   139  		refRenames[parameterPrefix+k] = parameterPrefix + v
   140  		if _, ok := s.Parameters[k]; ok {
   141  			foundOne = true
   142  		}
   143  	}
   144  
   145  	if !foundOne {
   146  		return s
   147  	}
   148  
   149  	ret := &spec.Swagger{}
   150  	*ret = *s
   151  
   152  	ret = schemamutation.ReplaceReferences(func(ref *spec.Ref) *spec.Ref {
   153  		refName := ref.String()
   154  		if newRef, found := refRenames[refName]; found {
   155  			ret := spec.MustCreateRef(newRef)
   156  			return &ret
   157  		}
   158  		return ref
   159  	}, ret)
   160  
   161  	renamed := make(map[string]spec.Parameter, len(ret.Parameters))
   162  	for k, v := range ret.Parameters {
   163  		if newRef, found := renames[k]; found {
   164  			k = newRef
   165  		}
   166  		renamed[k] = v
   167  	}
   168  	ret.Parameters = renamed
   169  
   170  	return ret
   171  }
   172  
   173  // MergeSpecsIgnorePathConflictRenamingDefinitionsAndParameters is the same as
   174  // MergeSpecs except it will ignore any path conflicts by keeping the paths of
   175  // destination. It will rename definition and parameter conflicts.
   176  func MergeSpecsIgnorePathConflictRenamingDefinitionsAndParameters(dest, source *spec.Swagger) error {
   177  	return mergeSpecs(dest, source, true, true, true)
   178  }
   179  
   180  // MergeSpecsIgnorePathConflictDeprecated is the same as MergeSpecs except it will ignore any path
   181  // conflicts by keeping the paths of destination. It will rename definition and
   182  // parameter conflicts.
   183  func MergeSpecsIgnorePathConflictDeprecated(dest, source *spec.Swagger) error {
   184  	return mergeSpecs(dest, source, true, false, true)
   185  }
   186  
   187  // MergeSpecsFailOnDefinitionConflict is different from MergeSpecs as it fails if there is
   188  // a definition or parameter conflict.
   189  func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error {
   190  	return mergeSpecs(dest, source, false, false, false)
   191  }
   192  
   193  // MergeSpecs copies paths, definitions and parameters from source to dest, rename
   194  // definitions if needed. It will fail on path conflicts.
   195  //
   196  // The destination is mutated, the source is not.
   197  func MergeSpecs(dest, source *spec.Swagger) error {
   198  	return mergeSpecs(dest, source, true, true, false)
   199  }
   200  
   201  // mergeSpecs merges source into dest while resolving conflicts.
   202  // The source is not mutated.
   203  func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, renameParameterConflicts, ignorePathConflicts bool) (err error) {
   204  	// Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
   205  	if source.Paths == nil {
   206  		// When a source spec does not have any path, that means none of the definitions
   207  		// are used thus we should not do anything
   208  		return nil
   209  	}
   210  	if dest.Paths == nil {
   211  		dest.Paths = &spec.Paths{}
   212  	}
   213  	if ignorePathConflicts {
   214  		keepPaths := []string{}
   215  		hasConflictingPath := false
   216  		for k := range source.Paths.Paths {
   217  			if _, found := dest.Paths.Paths[k]; !found {
   218  				keepPaths = append(keepPaths, k)
   219  			} else {
   220  				hasConflictingPath = true
   221  			}
   222  		}
   223  		if len(keepPaths) == 0 {
   224  			// There is nothing to merge. All paths are conflicting.
   225  			return nil
   226  		}
   227  		if hasConflictingPath {
   228  			source = FilterSpecByPathsWithoutSideEffects(source, keepPaths)
   229  		}
   230  	}
   231  
   232  	// Check for model conflicts and rename to make definitions conflict-free (modulo different GVKs)
   233  	usedNames := map[string]bool{}
   234  	for k := range dest.Definitions {
   235  		usedNames[k] = true
   236  	}
   237  	renames := map[string]string{}
   238  DEFINITIONLOOP:
   239  	for k, v := range source.Definitions {
   240  		existing, found := dest.Definitions[k]
   241  		if !found || deepEqualDefinitionsModuloGVKs(&existing, &v) {
   242  			// skip for now, we copy them after the rename loop
   243  			continue
   244  		}
   245  
   246  		if !renameModelConflicts {
   247  			return fmt.Errorf("model name conflict in merging OpenAPI spec: %s", k)
   248  		}
   249  
   250  		// Reuse previously renamed model if one exists
   251  		var newName string
   252  		i := 1
   253  		for found {
   254  			i++
   255  			newName = fmt.Sprintf("%s_v%d", k, i)
   256  			existing, found = dest.Definitions[newName]
   257  			if found && deepEqualDefinitionsModuloGVKs(&existing, &v) {
   258  				renames[k] = newName
   259  				continue DEFINITIONLOOP
   260  			}
   261  		}
   262  
   263  		_, foundInSource := source.Definitions[newName]
   264  		for usedNames[newName] || foundInSource {
   265  			i++
   266  			newName = fmt.Sprintf("%s_v%d", k, i)
   267  			_, foundInSource = source.Definitions[newName]
   268  		}
   269  		renames[k] = newName
   270  		usedNames[newName] = true
   271  	}
   272  	source = renameDefinitions(source, renames)
   273  
   274  	// Check for parameter conflicts and rename to make parameters conflict-free
   275  	usedNames = map[string]bool{}
   276  	for k := range dest.Parameters {
   277  		usedNames[k] = true
   278  	}
   279  	renames = map[string]string{}
   280  PARAMETERLOOP:
   281  	for k, p := range source.Parameters {
   282  		existing, found := dest.Parameters[k]
   283  		if !found || reflect.DeepEqual(&existing, &p) {
   284  			// skip for now, we copy them after the rename loop
   285  			continue
   286  		}
   287  
   288  		if !renameParameterConflicts {
   289  			return fmt.Errorf("parameter name conflict in merging OpenAPI spec: %s", k)
   290  		}
   291  
   292  		// Reuse previously renamed parameter if one exists
   293  		var newName string
   294  		i := 1
   295  		for found {
   296  			i++
   297  			newName = fmt.Sprintf("%s_v%d", k, i)
   298  			existing, found = dest.Parameters[newName]
   299  			if found && reflect.DeepEqual(&existing, &p) {
   300  				renames[k] = newName
   301  				continue PARAMETERLOOP
   302  			}
   303  		}
   304  
   305  		_, foundInSource := source.Parameters[newName]
   306  		for usedNames[newName] || foundInSource {
   307  			i++
   308  			newName = fmt.Sprintf("%s_v%d", k, i)
   309  			_, foundInSource = source.Parameters[newName]
   310  		}
   311  		renames[k] = newName
   312  		usedNames[newName] = true
   313  	}
   314  	source = renameParameters(source, renames)
   315  
   316  	// Now without conflict (modulo different GVKs), copy definitions to dest
   317  	for k, v := range source.Definitions {
   318  		if existing, found := dest.Definitions[k]; !found {
   319  			if dest.Definitions == nil {
   320  				dest.Definitions = make(spec.Definitions, len(source.Definitions))
   321  			}
   322  			dest.Definitions[k] = v
   323  		} else if merged, changed, err := mergedGVKs(&existing, &v); err != nil {
   324  			return err
   325  		} else if changed {
   326  			existing.Extensions[gvkKey] = merged
   327  		}
   328  	}
   329  
   330  	// Now without conflict, copy parameters to dest
   331  	for k, v := range source.Parameters {
   332  		if _, found := dest.Parameters[k]; !found {
   333  			if dest.Parameters == nil {
   334  				dest.Parameters = make(map[string]spec.Parameter, len(source.Parameters))
   335  			}
   336  			dest.Parameters[k] = v
   337  		}
   338  	}
   339  
   340  	// Check for path conflicts
   341  	for k, v := range source.Paths.Paths {
   342  		if _, found := dest.Paths.Paths[k]; found {
   343  			return fmt.Errorf("unable to merge: duplicated path %s", k)
   344  		}
   345  		// PathItem may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering).
   346  		if dest.Paths.Paths == nil {
   347  			dest.Paths.Paths = map[string]spec.PathItem{}
   348  		}
   349  		dest.Paths.Paths[k] = v
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  // deepEqualDefinitionsModuloGVKs compares s1 and s2, but ignores the x-kubernetes-group-version-kind extension.
   356  func deepEqualDefinitionsModuloGVKs(s1, s2 *spec.Schema) bool {
   357  	if s1 == nil {
   358  		return s2 == nil
   359  	} else if s2 == nil {
   360  		return false
   361  	}
   362  	if !reflect.DeepEqual(s1.Extensions, s2.Extensions) {
   363  		for k, v := range s1.Extensions {
   364  			if k == gvkKey {
   365  				continue
   366  			}
   367  			if !reflect.DeepEqual(v, s2.Extensions[k]) {
   368  				return false
   369  			}
   370  		}
   371  		len1 := len(s1.Extensions)
   372  		len2 := len(s2.Extensions)
   373  		if _, found := s1.Extensions[gvkKey]; found {
   374  			len1--
   375  		}
   376  		if _, found := s2.Extensions[gvkKey]; found {
   377  			len2--
   378  		}
   379  		if len1 != len2 {
   380  			return false
   381  		}
   382  
   383  		if s1.Extensions != nil {
   384  			shallowCopy := *s1
   385  			s1 = &shallowCopy
   386  			s1.Extensions = nil
   387  		}
   388  		if s2.Extensions != nil {
   389  			shallowCopy := *s2
   390  			s2 = &shallowCopy
   391  			s2.Extensions = nil
   392  		}
   393  	}
   394  
   395  	return reflect.DeepEqual(s1, s2)
   396  }
   397  
   398  // mergedGVKs merges the x-kubernetes-group-version-kind slices and returns the result, and whether
   399  // s1's x-kubernetes-group-version-kind slice was changed at all.
   400  func mergedGVKs(s1, s2 *spec.Schema) (interface{}, bool, error) {
   401  	gvk1, found1 := s1.Extensions[gvkKey]
   402  	gvk2, found2 := s2.Extensions[gvkKey]
   403  
   404  	if !found1 {
   405  		return gvk2, found2, nil
   406  	}
   407  	if !found2 {
   408  		return gvk1, false, nil
   409  	}
   410  
   411  	slice1, ok := gvk1.([]interface{})
   412  	if !ok {
   413  		return nil, false, fmt.Errorf("expected slice of GroupVersionKinds, got: %+v", slice1)
   414  	}
   415  	slice2, ok := gvk2.([]interface{})
   416  	if !ok {
   417  		return nil, false, fmt.Errorf("expected slice of GroupVersionKinds, got: %+v", slice2)
   418  	}
   419  
   420  	ret := make([]interface{}, len(slice1), len(slice1)+len(slice2))
   421  	keys := make([]string, 0, len(slice1)+len(slice2))
   422  	copy(ret, slice1)
   423  	seen := make(map[string]bool, len(slice1))
   424  	for _, x := range slice1 {
   425  		gvk, ok := x.(map[string]interface{})
   426  		if !ok {
   427  			return nil, false, fmt.Errorf(`expected {"group": <group>, "kind": <kind>, "version": <version>}, got: %#v`, x)
   428  		}
   429  		k := fmt.Sprintf("%s/%s.%s", gvk["group"], gvk["version"], gvk["kind"])
   430  		keys = append(keys, k)
   431  		seen[k] = true
   432  	}
   433  	changed := false
   434  	for _, x := range slice2 {
   435  		gvk, ok := x.(map[string]interface{})
   436  		if !ok {
   437  			return nil, false, fmt.Errorf(`expected {"group": <group>, "kind": <kind>, "version": <version>}, got: %#v`, x)
   438  		}
   439  		k := fmt.Sprintf("%s/%s.%s", gvk["group"], gvk["version"], gvk["kind"])
   440  		if seen[k] {
   441  			continue
   442  		}
   443  		ret = append(ret, x)
   444  		keys = append(keys, k)
   445  		changed = true
   446  	}
   447  
   448  	if changed {
   449  		sort.Sort(byKeys{ret, keys})
   450  	}
   451  
   452  	return ret, changed, nil
   453  }
   454  
   455  type byKeys struct {
   456  	values []interface{}
   457  	keys   []string
   458  }
   459  
   460  func (b byKeys) Len() int {
   461  	return len(b.values)
   462  }
   463  
   464  func (b byKeys) Less(i, j int) bool {
   465  	return b.keys[i] < b.keys[j]
   466  }
   467  
   468  func (b byKeys) Swap(i, j int) {
   469  	b.values[i], b.values[j] = b.values[j], b.values[i]
   470  	b.keys[i], b.keys[j] = b.keys[j], b.keys[i]
   471  }