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