mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-10 01:19:29 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
335 lines
8.7 KiB
Go
335 lines
8.7 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package schemaconv
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"sigs.k8s.io/structured-merge-diff/v4/schema"
|
|
)
|
|
|
|
const (
|
|
quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
|
|
rawExtensionResource = "io.k8s.apimachinery.pkg.runtime.RawExtension"
|
|
)
|
|
|
|
type convert struct {
|
|
preserveUnknownFields bool
|
|
output *schema.Schema
|
|
|
|
currentName string
|
|
current *schema.Atom
|
|
errorMessages []string
|
|
}
|
|
|
|
func (c *convert) push(name string, a *schema.Atom) *convert {
|
|
return &convert{
|
|
preserveUnknownFields: c.preserveUnknownFields,
|
|
output: c.output,
|
|
currentName: name,
|
|
current: a,
|
|
}
|
|
}
|
|
|
|
func (c *convert) top() *schema.Atom { return c.current }
|
|
|
|
func (c *convert) pop(c2 *convert) {
|
|
c.errorMessages = append(c.errorMessages, c2.errorMessages...)
|
|
}
|
|
|
|
func (c *convert) reportError(format string, args ...interface{}) {
|
|
c.errorMessages = append(c.errorMessages,
|
|
c.currentName+": "+fmt.Sprintf(format, args...),
|
|
)
|
|
}
|
|
|
|
func (c *convert) insertTypeDef(name string, atom schema.Atom) {
|
|
def := schema.TypeDef{
|
|
Name: name,
|
|
Atom: atom,
|
|
}
|
|
if def.Atom == (schema.Atom{}) {
|
|
// This could happen if there were a top-level reference.
|
|
return
|
|
}
|
|
c.output.Types = append(c.output.Types, def)
|
|
}
|
|
|
|
func (c *convert) addCommonTypes() {
|
|
c.output.Types = append(c.output.Types, untypedDef)
|
|
c.output.Types = append(c.output.Types, deducedDef)
|
|
}
|
|
|
|
var untypedName string = "__untyped_atomic_"
|
|
|
|
var untypedDef schema.TypeDef = schema.TypeDef{
|
|
Name: untypedName,
|
|
Atom: schema.Atom{
|
|
Scalar: ptr(schema.Scalar("untyped")),
|
|
List: &schema.List{
|
|
ElementType: schema.TypeRef{
|
|
NamedType: &untypedName,
|
|
},
|
|
ElementRelationship: schema.Atomic,
|
|
},
|
|
Map: &schema.Map{
|
|
ElementType: schema.TypeRef{
|
|
NamedType: &untypedName,
|
|
},
|
|
ElementRelationship: schema.Atomic,
|
|
},
|
|
},
|
|
}
|
|
|
|
var deducedName string = "__untyped_deduced_"
|
|
|
|
var deducedDef schema.TypeDef = schema.TypeDef{
|
|
Name: deducedName,
|
|
Atom: schema.Atom{
|
|
Scalar: ptr(schema.Scalar("untyped")),
|
|
List: &schema.List{
|
|
ElementType: schema.TypeRef{
|
|
NamedType: &untypedName,
|
|
},
|
|
ElementRelationship: schema.Atomic,
|
|
},
|
|
Map: &schema.Map{
|
|
ElementType: schema.TypeRef{
|
|
NamedType: &deducedName,
|
|
},
|
|
ElementRelationship: schema.Separable,
|
|
},
|
|
},
|
|
}
|
|
|
|
func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) {
|
|
schemaUnions := []schema.Union{}
|
|
if iunions, ok := extensions["x-kubernetes-unions"]; ok {
|
|
unions, ok := iunions.([]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf(`"x-kubernetes-unions" should be a list, got %#v`, unions)
|
|
}
|
|
for _, iunion := range unions {
|
|
union, ok := iunion.(map[interface{}]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf(`"x-kubernetes-unions" items should be a map of string to unions, got %#v`, iunion)
|
|
}
|
|
unionMap := map[string]interface{}{}
|
|
for k, v := range union {
|
|
key, ok := k.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf(`"x-kubernetes-unions" has non-string key: %#v`, k)
|
|
}
|
|
unionMap[key] = v
|
|
}
|
|
schemaUnion, err := makeUnion(unionMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
schemaUnions = append(schemaUnions, schemaUnion)
|
|
}
|
|
}
|
|
|
|
// Make sure we have no overlap between unions
|
|
fs := map[string]struct{}{}
|
|
for _, u := range schemaUnions {
|
|
if u.Discriminator != nil {
|
|
if _, ok := fs[*u.Discriminator]; ok {
|
|
return nil, fmt.Errorf("%v field appears multiple times in unions", *u.Discriminator)
|
|
}
|
|
fs[*u.Discriminator] = struct{}{}
|
|
}
|
|
for _, f := range u.Fields {
|
|
if _, ok := fs[f.FieldName]; ok {
|
|
return nil, fmt.Errorf("%v field appears multiple times in unions", f.FieldName)
|
|
}
|
|
fs[f.FieldName] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return schemaUnions, nil
|
|
}
|
|
|
|
func makeUnion(extensions map[string]interface{}) (schema.Union, error) {
|
|
union := schema.Union{
|
|
Fields: []schema.UnionField{},
|
|
}
|
|
|
|
if idiscriminator, ok := extensions["discriminator"]; ok {
|
|
discriminator, ok := idiscriminator.(string)
|
|
if !ok {
|
|
return schema.Union{}, fmt.Errorf(`"discriminator" must be a string, got: %#v`, idiscriminator)
|
|
}
|
|
union.Discriminator = &discriminator
|
|
}
|
|
|
|
if ifields, ok := extensions["fields-to-discriminateBy"]; ok {
|
|
fields, ok := ifields.(map[interface{}]interface{})
|
|
if !ok {
|
|
return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy" must be a map[string]string, got: %#v`, ifields)
|
|
}
|
|
// Needs sorted keys by field.
|
|
keys := []string{}
|
|
for ifield := range fields {
|
|
field, ok := ifield.(string)
|
|
if !ok {
|
|
return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy": field must be a string, got: %#v`, ifield)
|
|
}
|
|
keys = append(keys, field)
|
|
|
|
}
|
|
sort.Strings(keys)
|
|
reverseMap := map[string]struct{}{}
|
|
for _, field := range keys {
|
|
value := fields[field]
|
|
discriminated, ok := value.(string)
|
|
if !ok {
|
|
return schema.Union{}, fmt.Errorf(`"fields-to-discriminateBy"/%v: value must be a string, got: %#v`, field, value)
|
|
}
|
|
union.Fields = append(union.Fields, schema.UnionField{
|
|
FieldName: field,
|
|
DiscriminatorValue: discriminated,
|
|
})
|
|
|
|
// Check that we don't have the same discriminateBy multiple times.
|
|
if _, ok := reverseMap[discriminated]; ok {
|
|
return schema.Union{}, fmt.Errorf("Multiple fields have the same discriminated name: %v", discriminated)
|
|
}
|
|
reverseMap[discriminated] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return union, nil
|
|
}
|
|
|
|
func toStringSlice(o interface{}) (out []string, ok bool) {
|
|
switch t := o.(type) {
|
|
case []interface{}:
|
|
for _, v := range t {
|
|
switch vt := v.(type) {
|
|
case string:
|
|
out = append(out, vt)
|
|
}
|
|
}
|
|
return out, true
|
|
case []string:
|
|
return t, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func ptr(s schema.Scalar) *schema.Scalar { return &s }
|
|
|
|
// Basic conversion functions to convert OpenAPI schema definitions to
|
|
// SMD Schema atoms
|
|
func convertPrimitive(typ string, format string) (a schema.Atom) {
|
|
switch typ {
|
|
case "integer":
|
|
a.Scalar = ptr(schema.Numeric)
|
|
case "number":
|
|
a.Scalar = ptr(schema.Numeric)
|
|
case "string":
|
|
switch format {
|
|
case "":
|
|
a.Scalar = ptr(schema.String)
|
|
case "byte":
|
|
// byte really means []byte and is encoded as a string.
|
|
a.Scalar = ptr(schema.String)
|
|
case "int-or-string":
|
|
a.Scalar = ptr(schema.Scalar("untyped"))
|
|
case "date-time":
|
|
a.Scalar = ptr(schema.Scalar("untyped"))
|
|
default:
|
|
a.Scalar = ptr(schema.Scalar("untyped"))
|
|
}
|
|
case "boolean":
|
|
a.Scalar = ptr(schema.Boolean)
|
|
default:
|
|
a.Scalar = ptr(schema.Scalar("untyped"))
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
func getListElementRelationship(ext map[string]any) (schema.ElementRelationship, []string, error) {
|
|
if val, ok := ext["x-kubernetes-list-type"]; ok {
|
|
switch val {
|
|
case "atomic":
|
|
return schema.Atomic, nil, nil
|
|
case "set":
|
|
return schema.Associative, nil, nil
|
|
case "map":
|
|
keys, ok := ext["x-kubernetes-list-map-keys"]
|
|
|
|
if !ok {
|
|
return schema.Associative, nil, fmt.Errorf("missing map keys")
|
|
}
|
|
|
|
keyNames, ok := toStringSlice(keys)
|
|
if !ok {
|
|
return schema.Associative, nil, fmt.Errorf("uninterpreted map keys: %#v", keys)
|
|
}
|
|
|
|
return schema.Associative, keyNames, nil
|
|
default:
|
|
return schema.Atomic, nil, fmt.Errorf("unknown list type %v", val)
|
|
}
|
|
} else if val, ok := ext["x-kubernetes-patch-strategy"]; ok {
|
|
switch val {
|
|
case "merge", "merge,retainKeys":
|
|
if key, ok := ext["x-kubernetes-patch-merge-key"]; ok {
|
|
keyName, ok := key.(string)
|
|
|
|
if !ok {
|
|
return schema.Associative, nil, fmt.Errorf("uninterpreted merge key: %#v", key)
|
|
}
|
|
|
|
return schema.Associative, []string{keyName}, nil
|
|
}
|
|
// It's not an error for x-kubernetes-patch-merge-key to be absent,
|
|
// it means it's a set
|
|
return schema.Associative, nil, nil
|
|
case "retainKeys":
|
|
return schema.Atomic, nil, nil
|
|
default:
|
|
return schema.Atomic, nil, fmt.Errorf("unknown patch strategy %v", val)
|
|
}
|
|
}
|
|
|
|
// Treat as atomic by default
|
|
return schema.Atomic, nil, nil
|
|
}
|
|
|
|
// Returns map element relationship if specified, or empty string if unspecified
|
|
func getMapElementRelationship(ext map[string]any) (schema.ElementRelationship, error) {
|
|
val, ok := ext["x-kubernetes-map-type"]
|
|
if !ok {
|
|
// unset Map element relationship
|
|
return "", nil
|
|
}
|
|
|
|
switch val {
|
|
case "atomic":
|
|
return schema.Atomic, nil
|
|
case "granular":
|
|
return schema.Separable, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown map type %v", val)
|
|
}
|
|
}
|