k8s.io/apiserver@v0.31.1/pkg/endpoints/openapi/openapi.go (about)

     1  /*
     2  Copyright 2016 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 openapi
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"strings"
    25  	"unicode"
    26  
    27  	restful "github.com/emicklei/go-restful/v3"
    28  
    29  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/kube-openapi/pkg/util"
    33  	"k8s.io/kube-openapi/pkg/validation/spec"
    34  )
    35  
    36  var verbs = util.NewTrie([]string{"get", "log", "read", "replace", "patch", "delete", "deletecollection", "watch", "connect", "proxy", "list", "create", "patch"})
    37  
    38  const (
    39  	extensionGVK = "x-kubernetes-group-version-kind"
    40  )
    41  
    42  // ToValidOperationID makes an string a valid op ID (e.g. removing punctuations and whitespaces and make it camel case)
    43  func ToValidOperationID(s string, capitalizeFirstLetter bool) string {
    44  	var buffer bytes.Buffer
    45  	capitalize := capitalizeFirstLetter
    46  	for i, r := range s {
    47  		if unicode.IsLetter(r) || r == '_' || (i != 0 && unicode.IsDigit(r)) {
    48  			if capitalize {
    49  				buffer.WriteRune(unicode.ToUpper(r))
    50  				capitalize = false
    51  			} else {
    52  				buffer.WriteRune(r)
    53  			}
    54  		} else {
    55  			capitalize = true
    56  		}
    57  	}
    58  	return buffer.String()
    59  }
    60  
    61  // GetOperationIDAndTags returns a customize operation ID and a list of tags for kubernetes API server's OpenAPI spec to prevent duplicate IDs.
    62  func GetOperationIDAndTags(r *restful.Route) (string, []string, error) {
    63  	op := r.Operation
    64  	path := r.Path
    65  	var tags []string
    66  	prefix, exists := verbs.GetPrefix(op)
    67  	if !exists {
    68  		return op, tags, fmt.Errorf("operation names should start with a verb. Cannot determine operation verb from %v", op)
    69  	}
    70  	op = op[len(prefix):]
    71  	parts := strings.Split(strings.Trim(path, "/"), "/")
    72  	// Assume /api is /apis/core, remove this when we actually server /api/... on /apis/core/...
    73  	if len(parts) >= 1 && parts[0] == "api" {
    74  		parts = append([]string{"apis", "core"}, parts[1:]...)
    75  	}
    76  	if len(parts) >= 2 && parts[0] == "apis" {
    77  		trimmed := strings.TrimSuffix(parts[1], ".k8s.io")
    78  		prefix = prefix + ToValidOperationID(trimmed, prefix != "")
    79  		tag := ToValidOperationID(trimmed, false)
    80  		if len(parts) > 2 {
    81  			prefix = prefix + ToValidOperationID(parts[2], prefix != "")
    82  			tag = tag + "_" + ToValidOperationID(parts[2], false)
    83  		}
    84  		tags = append(tags, tag)
    85  	} else if len(parts) >= 1 {
    86  		tags = append(tags, ToValidOperationID(parts[0], false))
    87  	}
    88  	return prefix + ToValidOperationID(op, prefix != ""), tags, nil
    89  }
    90  
    91  type groupVersionKinds []v1.GroupVersionKind
    92  
    93  func (s groupVersionKinds) Len() int {
    94  	return len(s)
    95  }
    96  
    97  func (s groupVersionKinds) Swap(i, j int) {
    98  	s[i], s[j] = s[j], s[i]
    99  }
   100  
   101  func (s groupVersionKinds) Less(i, j int) bool {
   102  	if s[i].Group == s[j].Group {
   103  		if s[i].Version == s[j].Version {
   104  			return s[i].Kind < s[j].Kind
   105  		}
   106  		return s[i].Version < s[j].Version
   107  	}
   108  	return s[i].Group < s[j].Group
   109  }
   110  
   111  func (s groupVersionKinds) JSON() []interface{} {
   112  	j := []interface{}{}
   113  	for _, gvk := range s {
   114  		j = append(j, map[string]interface{}{
   115  			"group":   gvk.Group,
   116  			"version": gvk.Version,
   117  			"kind":    gvk.Kind,
   118  		})
   119  	}
   120  	return j
   121  }
   122  
   123  // DefinitionNamer is the type to customize OpenAPI definition name.
   124  type DefinitionNamer struct {
   125  	typeGroupVersionKinds map[string]groupVersionKinds
   126  }
   127  
   128  func gvkConvert(gvk schema.GroupVersionKind) v1.GroupVersionKind {
   129  	return v1.GroupVersionKind{
   130  		Group:   gvk.Group,
   131  		Version: gvk.Version,
   132  		Kind:    gvk.Kind,
   133  	}
   134  }
   135  
   136  func friendlyName(name string) string {
   137  	nameParts := strings.Split(name, "/")
   138  	// Reverse first part. e.g., io.k8s... instead of k8s.io...
   139  	if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") {
   140  		parts := strings.Split(nameParts[0], ".")
   141  		for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 {
   142  			parts[i], parts[j] = parts[j], parts[i]
   143  		}
   144  		nameParts[0] = strings.Join(parts, ".")
   145  	}
   146  	return strings.Join(nameParts, ".")
   147  }
   148  
   149  func typeName(t reflect.Type) string {
   150  	path := t.PkgPath()
   151  	if strings.Contains(path, "/vendor/") {
   152  		path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
   153  	}
   154  	return fmt.Sprintf("%s.%s", path, t.Name())
   155  }
   156  
   157  // NewDefinitionNamer constructs a new DefinitionNamer to be used to customize OpenAPI spec.
   158  func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer {
   159  	ret := &DefinitionNamer{
   160  		typeGroupVersionKinds: map[string]groupVersionKinds{},
   161  	}
   162  	for _, s := range schemes {
   163  		for gvk, rtype := range s.AllKnownTypes() {
   164  			newGVK := gvkConvert(gvk)
   165  			exists := false
   166  			for _, existingGVK := range ret.typeGroupVersionKinds[typeName(rtype)] {
   167  				if newGVK == existingGVK {
   168  					exists = true
   169  					break
   170  				}
   171  			}
   172  			if !exists {
   173  				ret.typeGroupVersionKinds[typeName(rtype)] = append(ret.typeGroupVersionKinds[typeName(rtype)], newGVK)
   174  			}
   175  		}
   176  	}
   177  	for _, gvk := range ret.typeGroupVersionKinds {
   178  		sort.Sort(gvk)
   179  	}
   180  	return ret
   181  }
   182  
   183  // GetDefinitionName returns the name and tags for a given definition
   184  func (d *DefinitionNamer) GetDefinitionName(name string) (string, spec.Extensions) {
   185  	if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok {
   186  		return friendlyName(name), spec.Extensions{
   187  			extensionGVK: groupVersionKinds.JSON(),
   188  		}
   189  	}
   190  	return friendlyName(name), nil
   191  }