mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
6f1bd5fff2
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Co-authored-by: shuting <shuting@nirmata.com>
292 lines
7.9 KiB
Go
292 lines
7.9 KiB
Go
package openapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/google/gnostic/compiler"
|
|
openapiv2 "github.com/google/gnostic/openapiv2"
|
|
"github.com/kyverno/kyverno/data"
|
|
"github.com/kyverno/kyverno/pkg/logging"
|
|
"golang.org/x/exp/slices"
|
|
"gopkg.in/yaml.v3"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
func parseGVK(str string) (group, apiVersion, kind string) {
|
|
if strings.Count(str, "/") == 0 {
|
|
return "", "", str
|
|
}
|
|
splitString := strings.Split(str, "/")
|
|
if strings.Count(str, "/") == 1 {
|
|
return "", splitString[0], splitString[1]
|
|
}
|
|
return splitString[0], splitString[1], splitString[2]
|
|
}
|
|
|
|
func groupMatches(gvkMap map[string]bool, group, kind string) bool {
|
|
if group == "" {
|
|
ok := gvkMap["core"]
|
|
if ok {
|
|
return true
|
|
}
|
|
} else {
|
|
elements := strings.Split(group, ".")
|
|
ok := gvkMap[elements[0]]
|
|
if ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// matchGVK is a helper function that checks if the given GVK matches the definition name
|
|
func matchGVK(definitionName, gvk string) bool {
|
|
paths := strings.Split(definitionName, ".")
|
|
|
|
gvkMap := make(map[string]bool)
|
|
for _, p := range paths {
|
|
gvkMap[p] = true
|
|
}
|
|
|
|
group, version, kind := parseGVK(gvk)
|
|
|
|
ok := gvkMap[kind]
|
|
if !ok {
|
|
return false
|
|
}
|
|
ok = gvkMap[version]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if !groupMatches(gvkMap, group, kind) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func getSchemaDocument() (*openapiv2.Document, error) {
|
|
var spec yaml.Node
|
|
err := yaml.Unmarshal([]byte(data.SwaggerDoc), &spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
root := spec.Content[0]
|
|
return openapiv2.NewDocument(root, compiler.NewContext("$root", root, nil))
|
|
}
|
|
|
|
func getArrayValue(kindSchema *openapiv2.Schema, o *manager) interface{} {
|
|
var array []interface{}
|
|
for _, schema := range kindSchema.GetItems().GetSchema() {
|
|
array = append(array, o.generateEmptyResource(schema))
|
|
}
|
|
|
|
return array
|
|
}
|
|
|
|
func getObjectValue(kindSchema *openapiv2.Schema, o *manager) interface{} {
|
|
props := make(map[string]interface{})
|
|
properties := kindSchema.GetProperties().GetAdditionalProperties()
|
|
if len(properties) == 0 {
|
|
return props
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
var mutex sync.Mutex
|
|
wg.Add(len(properties))
|
|
for _, property := range properties {
|
|
go func(property *openapiv2.NamedSchema) {
|
|
prop := o.generateEmptyResource(property.GetValue())
|
|
mutex.Lock()
|
|
props[property.GetName()] = prop
|
|
mutex.Unlock()
|
|
wg.Done()
|
|
}(property)
|
|
}
|
|
wg.Wait()
|
|
return props
|
|
}
|
|
|
|
func getBoolValue(kindSchema *openapiv2.Schema) bool {
|
|
if d := kindSchema.GetDefault(); d != nil {
|
|
v := getAnyValue(d)
|
|
return string(v) == "true"
|
|
}
|
|
|
|
if e := kindSchema.GetExample(); e != nil {
|
|
v := getAnyValue(e)
|
|
return string(v) == "true"
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func getNumericValue(kindSchema *openapiv2.Schema) int64 {
|
|
if d := kindSchema.GetDefault(); d != nil {
|
|
v := getAnyValue(d)
|
|
val, _ := strconv.Atoi(string(v))
|
|
return int64(val)
|
|
}
|
|
|
|
if e := kindSchema.GetExample(); e != nil {
|
|
v := getAnyValue(e)
|
|
val, _ := strconv.Atoi(string(v))
|
|
return int64(val)
|
|
}
|
|
|
|
return int64(0)
|
|
}
|
|
|
|
func getStringValue(kindSchema *openapiv2.Schema) string {
|
|
if d := kindSchema.GetDefault(); d != nil {
|
|
v := getAnyValue(d)
|
|
return string(v)
|
|
}
|
|
|
|
if e := kindSchema.GetExample(); e != nil {
|
|
v := getAnyValue(e)
|
|
return string(v)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func getAnyValue(any *openapiv2.Any) []byte {
|
|
if any != nil {
|
|
if val := any.GetValue(); val != nil {
|
|
return val.GetValue()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getAllAPIVersions gets all available versions for a kind
|
|
// returns a map which stores all kinds with its versions
|
|
func getAllAPIVersions(apiResourceLists []*metav1.APIResourceList) map[string]apiVersions {
|
|
tempKindToAPIVersions := make(map[string]apiVersions)
|
|
|
|
for _, apiResourceList := range apiResourceLists {
|
|
lastKind := ""
|
|
for _, apiResource := range apiResourceList.APIResources {
|
|
if apiResource.Kind == lastKind {
|
|
continue
|
|
}
|
|
|
|
version, ok := tempKindToAPIVersions[apiResource.Kind]
|
|
if !ok {
|
|
tempKindToAPIVersions[apiResource.Kind] = apiVersions{}
|
|
}
|
|
|
|
gvk := strings.Join([]string{apiResourceList.GroupVersion, apiResource.Kind}, "/")
|
|
version.gvks = append(version.gvks, gvk)
|
|
tempKindToAPIVersions[apiResource.Kind] = version
|
|
lastKind = apiResource.Kind
|
|
}
|
|
}
|
|
|
|
return tempKindToAPIVersions
|
|
}
|
|
|
|
// setPreferredVersions sets the serverPreferredGVK of the given apiVersions map
|
|
func setPreferredVersions(kindToAPIVersions map[string]apiVersions, preferredAPIResourcesLists []*metav1.APIResourceList) map[string]apiVersions {
|
|
tempKindToAPIVersionsCopied := copyKindToAPIVersions(kindToAPIVersions)
|
|
|
|
for kind, versions := range tempKindToAPIVersionsCopied {
|
|
for _, preferredAPIResourcesList := range preferredAPIResourcesLists {
|
|
for _, resource := range preferredAPIResourcesList.APIResources {
|
|
preferredGV := preferredAPIResourcesList.GroupVersion
|
|
preferredGVK := preferredGV + "/" + resource.Kind
|
|
|
|
if slices.Contains(versions.gvks, preferredGVK) {
|
|
v := kindToAPIVersions[kind]
|
|
|
|
// if a Kind belongs to multiple groups, the first group/version
|
|
// returned from discovery docs is used as preferred version
|
|
// https://github.com/kubernetes/kubernetes/issues/94761#issuecomment-691982480
|
|
if v.serverPreferredGVK != "" {
|
|
continue
|
|
}
|
|
|
|
v.serverPreferredGVK = strings.Join([]string{preferredGV, kind}, "/")
|
|
kindToAPIVersions[kind] = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return kindToAPIVersions
|
|
}
|
|
|
|
func copyKindToAPIVersions(old map[string]apiVersions) map[string]apiVersions {
|
|
new := make(map[string]apiVersions, len(old))
|
|
for key, value := range old {
|
|
new[key] = value
|
|
}
|
|
return new
|
|
}
|
|
|
|
func getAPIResourceLists() ([]*metav1.APIResourceList, []*metav1.APIResourceList, error) {
|
|
var apiResourceLists []*metav1.APIResourceList
|
|
err := json.Unmarshal([]byte(data.APIResourceLists), &apiResourceLists)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to load apiResourceLists: %v", err)
|
|
}
|
|
|
|
var preferredAPIResourcesLists []*metav1.APIResourceList
|
|
err = json.Unmarshal([]byte(data.APIResourceLists), &preferredAPIResourcesLists)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to load preferredAPIResourcesLists: %v", err)
|
|
}
|
|
|
|
return apiResourceLists, preferredAPIResourcesLists, nil
|
|
}
|
|
|
|
func isOpenV3Error(err error) bool {
|
|
unsupportedValues := []string{"anyOf", "allOf", "not"}
|
|
v3valueFound := false
|
|
for _, value := range unsupportedValues {
|
|
if !strings.Contains(err.Error(), fmt.Sprintf("has invalid property: %s", value)) {
|
|
v3valueFound = true
|
|
break
|
|
}
|
|
}
|
|
return v3valueFound
|
|
}
|
|
|
|
// addingDefaultFieldsToSchema will add any default missing fields like apiVersion, metadata
|
|
func addingDefaultFieldsToSchema(crdName string, schemaRaw []byte) ([]byte, error) {
|
|
var schema struct {
|
|
Properties map[string]interface{} `json:"properties"`
|
|
}
|
|
_ = json.Unmarshal(schemaRaw, &schema)
|
|
|
|
if len(schema.Properties) < 1 {
|
|
logging.V(6).Info("crd schema has no properties", "name", crdName)
|
|
return schemaRaw, nil
|
|
}
|
|
|
|
if schema.Properties["apiVersion"] == nil {
|
|
apiVersionDefRaw := `{"description":"APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources","type":"string"}`
|
|
apiVersionDef := make(map[string]interface{})
|
|
_ = json.Unmarshal([]byte(apiVersionDefRaw), &apiVersionDef)
|
|
schema.Properties["apiVersion"] = apiVersionDef
|
|
}
|
|
|
|
if schema.Properties["metadata"] == nil {
|
|
metadataDefRaw := `{"$ref":"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta","description":"Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata"}`
|
|
metadataDef := make(map[string]interface{})
|
|
_ = json.Unmarshal([]byte(metadataDefRaw), &metadataDef)
|
|
schema.Properties["metadata"] = metadataDef
|
|
}
|
|
|
|
schemaWithDefaultFields, _ := json.Marshal(schema)
|
|
|
|
return schemaWithDefaultFields, nil
|
|
}
|