istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/schema/resource/schema.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package resource 16 17 import ( 18 "errors" 19 "fmt" 20 "reflect" 21 22 "github.com/hashicorp/go-multierror" 23 "google.golang.org/protobuf/reflect/protoreflect" 24 "google.golang.org/protobuf/reflect/protoregistry" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 27 "istio.io/istio/pkg/config" 28 "istio.io/istio/pkg/config/labels" 29 "istio.io/istio/pkg/config/validation" 30 ) 31 32 // Schema for a resource. 33 type Schema interface { 34 fmt.Stringer 35 36 // GroupVersionKind of the resource. This is the only way to uniquely identify a resource. 37 GroupVersionKind() config.GroupVersionKind 38 39 // GroupVersionResource of the resource. 40 GroupVersionResource() schema.GroupVersionResource 41 42 // IsClusterScoped indicates that this resource is scoped to a particular namespace within a cluster. 43 IsClusterScoped() bool 44 45 // IsBuiltin indicates that this resource is builtin (not a CRD) 46 IsBuiltin() bool 47 48 // Identifier returns a unique identifier for the resource 49 Identifier() string 50 51 // Kind for this resource. 52 Kind() string 53 54 // Plural returns the plural form of the Kind. 55 Plural() string 56 57 // Group for this resource. 58 Group() string 59 60 // Version of this resource. 61 Version() string 62 63 // GroupVersionAliasKinds is the GVK of this resource, 64 // but the version is from its version aliases to perform version conversion. 65 GroupVersionAliasKinds() []config.GroupVersionKind 66 67 // APIVersion is a utility that returns a k8s API version string of the form "Group/Version". 68 APIVersion() string 69 70 // Proto returns the protocol buffer type name for this resource. 71 Proto() string 72 73 // ProtoPackage returns the golang package for the protobuf resource. 74 ProtoPackage() string 75 76 // NewInstance returns a new instance of the protocol buffer message for this resource. 77 NewInstance() (config.Spec, error) 78 79 // Status returns the associated status of the schema 80 Status() (config.Status, error) 81 82 // StatusKind returns the Kind of the status field. If unset, the field does not support status. 83 StatusKind() string 84 StatusPackage() string 85 86 // MustNewInstance calls NewInstance and panics if an error occurs. 87 MustNewInstance() config.Spec 88 89 // Validate this schema. 90 Validate() error 91 92 // ValidateConfig validates that the given config message is of the correct type for this schema 93 // and that the contents are valid. 94 ValidateConfig(cfg config.Config) (validation.Warning, error) 95 96 // Equal is a helper function for testing equality between Schema instances. This supports comparison 97 // with the cmp library. 98 Equal(other Schema) bool 99 } 100 101 // Builder for a Schema. 102 type Builder struct { 103 // ClusterScoped is true for resource in cluster-level. 104 ClusterScoped bool 105 106 // Synthetic is true for resource that do not actually exist in a cluster 107 Synthetic bool 108 109 // Builtin is true for resources that are builtin (not CRD) 110 Builtin bool 111 112 // Identifier is the unique identifier for the resource 113 Identifier string 114 115 // Kind is the config proto type. 116 Kind string 117 118 // Plural is the type in plural. 119 Plural string 120 121 // Group is the config proto group. 122 Group string 123 124 // Version is the config proto version. 125 Version string 126 127 // VersionAliases is the config proto version aliases. 128 VersionAliases []string 129 130 // Proto refers to the protobuf message type name corresponding to the type 131 Proto string 132 133 StatusProto string 134 135 // ReflectType is the type of the go struct 136 ReflectType reflect.Type 137 138 // StatusType is the type of the associated status. 139 StatusType reflect.Type 140 141 // ProtoPackage refers to the name of golang package for the protobuf message. 142 ProtoPackage string 143 144 // StatusPackage refers to the name of the golang status package. 145 StatusPackage string 146 147 // ValidateProto performs validation on protobuf messages based on this schema. 148 ValidateProto validation.ValidateFunc 149 } 150 151 // Build a Schema instance. 152 func (b Builder) Build() (Schema, error) { 153 s := b.BuildNoValidate() 154 155 // Validate the schema. 156 if err := s.Validate(); err != nil { 157 return nil, err 158 } 159 160 return s, nil 161 } 162 163 // MustBuild calls Build and panics if it fails. 164 func (b Builder) MustBuild() Schema { 165 s, err := b.Build() 166 if err != nil { 167 panic(fmt.Sprintf("MustBuild: %v", err)) 168 } 169 return s 170 } 171 172 // BuildNoValidate builds the Schema without checking the fields. 173 func (b Builder) BuildNoValidate() Schema { 174 if b.ValidateProto == nil { 175 b.ValidateProto = validation.EmptyValidate 176 } 177 178 return &schemaImpl{ 179 clusterScoped: b.ClusterScoped, 180 synthetic: b.Synthetic, 181 builtin: b.Builtin, 182 gvk: config.GroupVersionKind{ 183 Group: b.Group, 184 Version: b.Version, 185 Kind: b.Kind, 186 }, 187 plural: b.Plural, 188 apiVersion: b.Group + "/" + b.Version, 189 versionAliases: b.VersionAliases, 190 proto: b.Proto, 191 goPackage: b.ProtoPackage, 192 identifier: b.Identifier, 193 reflectType: b.ReflectType, 194 validateConfig: b.ValidateProto, 195 statusType: b.StatusType, 196 statusPackage: b.StatusPackage, 197 } 198 } 199 200 type schemaImpl struct { 201 clusterScoped bool 202 builtin bool 203 gvk config.GroupVersionKind 204 versionAliases []string 205 plural string 206 apiVersion string 207 proto string 208 goPackage string 209 validateConfig validation.ValidateFunc 210 reflectType reflect.Type 211 statusType reflect.Type 212 statusPackage string 213 identifier string 214 synthetic bool 215 } 216 217 func (s *schemaImpl) GroupVersionKind() config.GroupVersionKind { 218 return s.gvk 219 } 220 221 func (s *schemaImpl) GroupVersionResource() schema.GroupVersionResource { 222 return schema.GroupVersionResource{ 223 Group: s.Group(), 224 Version: s.Version(), 225 Resource: s.Plural(), 226 } 227 } 228 229 func (s *schemaImpl) IsClusterScoped() bool { 230 return s.clusterScoped 231 } 232 233 func (s *schemaImpl) IsBuiltin() bool { 234 return s.builtin 235 } 236 237 func (s *schemaImpl) Identifier() string { 238 return s.identifier 239 } 240 241 func (s *schemaImpl) Kind() string { 242 return s.gvk.Kind 243 } 244 245 func (s *schemaImpl) Plural() string { 246 return s.plural 247 } 248 249 func (s *schemaImpl) Group() string { 250 return s.gvk.Group 251 } 252 253 func (s *schemaImpl) Version() string { 254 return s.gvk.Version 255 } 256 257 func (s *schemaImpl) GroupVersionAliasKinds() []config.GroupVersionKind { 258 gvks := make([]config.GroupVersionKind, len(s.versionAliases)) 259 for i, va := range s.versionAliases { 260 gvks[i] = s.gvk 261 gvks[i].Version = va 262 } 263 gvks = append(gvks, s.GroupVersionKind()) 264 return gvks 265 } 266 267 func (s *schemaImpl) APIVersion() string { 268 return s.apiVersion 269 } 270 271 func (s *schemaImpl) Proto() string { 272 return s.proto 273 } 274 275 func (s *schemaImpl) ProtoPackage() string { 276 return s.goPackage 277 } 278 279 func (s *schemaImpl) StatusPackage() string { 280 return s.statusPackage 281 } 282 283 func (s *schemaImpl) Validate() (err error) { 284 if !labels.IsDNS1123Label(s.Kind()) { 285 err = multierror.Append(err, fmt.Errorf("invalid kind: %s", s.Kind())) 286 } 287 if !labels.IsDNS1123Label(s.plural) { 288 err = multierror.Append(err, fmt.Errorf("invalid plural for kind %s: %s", s.Kind(), s.plural)) 289 } 290 if s.reflectType == nil && getProtoMessageType(s.proto) == nil { 291 err = multierror.Append(err, fmt.Errorf("proto message or reflect type not found: %v", s.proto)) 292 } 293 return 294 } 295 296 func (s *schemaImpl) String() string { 297 return fmt.Sprintf("[Schema](%s, %q, %s)", s.Kind(), s.goPackage, s.proto) 298 } 299 300 func (s *schemaImpl) NewInstance() (config.Spec, error) { 301 rt := s.reflectType 302 var instance any 303 if rt == nil { 304 // Use proto 305 t, err := protoMessageType(protoreflect.FullName(s.proto)) 306 if err != nil || t == nil { 307 return nil, errors.New("failed to find reflect type") 308 } 309 instance = t.New().Interface() 310 } else { 311 instance = reflect.New(rt).Interface() 312 } 313 314 p, ok := instance.(config.Spec) 315 if !ok { 316 return nil, fmt.Errorf( 317 "newInstance: message is not an instance of config.Spec. kind:%s, type:%v, value:%v", 318 s.Kind(), rt, instance) 319 } 320 return p, nil 321 } 322 323 func (s *schemaImpl) Status() (config.Status, error) { 324 statTyp := s.statusType 325 if statTyp == nil { 326 return nil, errors.New("unknown status type") 327 } 328 instance := reflect.New(statTyp).Interface() 329 p, ok := instance.(config.Status) 330 if !ok { 331 return nil, fmt.Errorf("status: statusType not an instance of config.Status. type: %v, value: %v", statTyp, instance) 332 } 333 return p, nil 334 } 335 336 func (s *schemaImpl) StatusKind() string { 337 if s.statusType == nil { 338 return "" 339 } 340 return s.statusType.Name() 341 } 342 343 func (s *schemaImpl) MustNewInstance() config.Spec { 344 p, err := s.NewInstance() 345 if err != nil { 346 panic(err) 347 } 348 return p 349 } 350 351 func (s *schemaImpl) ValidateConfig(cfg config.Config) (validation.Warning, error) { 352 return s.validateConfig(cfg) 353 } 354 355 func (s *schemaImpl) Equal(o Schema) bool { 356 return s.IsClusterScoped() == o.IsClusterScoped() && 357 s.Kind() == o.Kind() && 358 s.Plural() == o.Plural() && 359 s.Group() == o.Group() && 360 s.Version() == o.Version() && 361 s.Proto() == o.Proto() && 362 s.ProtoPackage() == o.ProtoPackage() 363 } 364 365 // FromKubernetesGVK converts a Kubernetes GVK to an Istio GVK 366 func FromKubernetesGVK(in *schema.GroupVersionKind) config.GroupVersionKind { 367 return config.GroupVersionKind{ 368 Group: in.Group, 369 Version: in.Version, 370 Kind: in.Kind, 371 } 372 } 373 374 // getProtoMessageType returns the Go lang type of the proto with the specified name. 375 func getProtoMessageType(protoMessageName string) reflect.Type { 376 t, err := protoMessageType(protoreflect.FullName(protoMessageName)) 377 if err != nil || t == nil { 378 return nil 379 } 380 return reflect.TypeOf(t.Zero().Interface()) 381 } 382 383 var protoMessageType = protoregistry.GlobalTypes.FindMessageByName