1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package parse 18 19import ( 20 "fmt" 21 "strings" 22 23 "k8s.io/kube-openapi/pkg/util/proto" 24) 25 26// Contains functions for casting openapi interfaces to their underlying types 27 28// getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference 29func getSchemaType(schema proto.Schema) string { 30 if schema == nil { 31 return "" 32 } 33 visitor := &baseSchemaVisitor{} 34 schema.Accept(visitor) 35 return visitor.Kind 36} 37 38// getKind converts schema to an *proto.Kind object 39func getKind(schema proto.Schema) (*proto.Kind, error) { 40 if schema == nil { 41 return nil, nil 42 } 43 visitor := &kindSchemaVisitor{} 44 schema.Accept(visitor) 45 return visitor.Result, visitor.Err 46} 47 48// getArray converts schema to an *proto.Array object 49func getArray(schema proto.Schema) (*proto.Array, error) { 50 if schema == nil { 51 return nil, nil 52 } 53 visitor := &arraySchemaVisitor{} 54 schema.Accept(visitor) 55 return visitor.Result, visitor.Err 56} 57 58// getMap converts schema to an *proto.Map object 59func getMap(schema proto.Schema) (*proto.Map, error) { 60 if schema == nil { 61 return nil, nil 62 } 63 visitor := &mapSchemaVisitor{} 64 schema.Accept(visitor) 65 return visitor.Result, visitor.Err 66} 67 68// getPrimitive converts schema to an *proto.Primitive object 69func getPrimitive(schema proto.Schema) (*proto.Primitive, error) { 70 if schema == nil { 71 return nil, nil 72 } 73 visitor := &primitiveSchemaVisitor{} 74 schema.Accept(visitor) 75 return visitor.Result, visitor.Err 76} 77 78type baseSchemaVisitor struct { 79 Err error 80 Kind string 81} 82 83// VisitArray implements openapi 84func (v *baseSchemaVisitor) VisitArray(array *proto.Array) { 85 v.Kind = "array" 86 v.Err = fmt.Errorf("Array type not expected") 87} 88 89// MergeMap implements openapi 90func (v *baseSchemaVisitor) VisitMap(*proto.Map) { 91 v.Kind = "map" 92 v.Err = fmt.Errorf("Map type not expected") 93} 94 95// MergePrimitive implements openapi 96func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) { 97 v.Kind = "primitive" 98 v.Err = fmt.Errorf("Primitive type not expected") 99} 100 101// VisitKind implements openapi 102func (v *baseSchemaVisitor) VisitKind(*proto.Kind) { 103 v.Kind = "kind" 104 v.Err = fmt.Errorf("Kind type not expected") 105} 106 107// VisitReference implements openapi 108func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) { 109 v.Kind = "reference" 110 v.Err = fmt.Errorf("Reference type not expected") 111} 112 113type kindSchemaVisitor struct { 114 baseSchemaVisitor 115 Result *proto.Kind 116} 117 118// VisitKind implements openapi 119func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) { 120 v.Result = result 121 v.Kind = "kind" 122} 123 124// VisitReference implements openapi 125func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) { 126 reference.SubSchema().Accept(v) 127 if v.Err == nil { 128 v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) 129 } 130} 131 132func copyExtensions(field string, from, to map[string]interface{}) error { 133 // Copy extensions from field to type for references 134 for key, val := range from { 135 if curr, found := to[key]; found { 136 // Don't allow the same extension to be defined both on the field and on the type 137 return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v", 138 key, field, curr, val) 139 } 140 to[key] = val 141 } 142 return nil 143} 144 145type mapSchemaVisitor struct { 146 baseSchemaVisitor 147 Result *proto.Map 148} 149 150// VisitMap implements openapi 151func (v *mapSchemaVisitor) VisitMap(result *proto.Map) { 152 v.Result = result 153 v.Kind = "map" 154} 155 156// VisitReference implements openapi 157func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) { 158 reference.SubSchema().Accept(v) 159 if v.Err == nil { 160 v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) 161 } 162} 163 164type arraySchemaVisitor struct { 165 baseSchemaVisitor 166 Result *proto.Array 167} 168 169// VisitArray implements openapi 170func (v *arraySchemaVisitor) VisitArray(result *proto.Array) { 171 v.Result = result 172 v.Kind = "array" 173 v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions()) 174} 175 176// copySubElementPatchStrategy copies the strategies to subelements to the subtype 177// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys" 178// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element 179func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error { 180 // Check if the parent has a patch strategy extension 181 if ext, found := from["x-kubernetes-patch-strategy"]; found { 182 strategy, ok := ext.(string) 183 if !ok { 184 return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T", 185 field, ext) 186 } 187 // Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type 188 if strings.Contains(strategy, ",") { 189 strategies := strings.Split(strategy, ",") 190 if len(strategies) != 2 { 191 // Only 1 sub strategy is supported 192 return fmt.Errorf( 193 "Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v", 194 strategies) 195 } 196 to["x-kubernetes-patch-strategy"] = strategies[1] 197 } 198 } 199 return nil 200} 201 202// VisitReference implements openapi 203func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) { 204 reference.SubSchema().Accept(v) 205 if v.Err == nil { 206 v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) 207 } 208} 209 210type primitiveSchemaVisitor struct { 211 baseSchemaVisitor 212 Result *proto.Primitive 213} 214 215// VisitPrimitive implements openapi 216func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) { 217 v.Result = result 218 v.Kind = "primitive" 219} 220 221// VisitReference implements openapi 222func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) { 223 reference.SubSchema().Accept(v) 224 if v.Err == nil { 225 v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions) 226 } 227} 228