blob: 4d50cb0c165781fd207922ef965ea4d2ee1caadf [file] [log] [blame]
// Copyright Istio 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 resource
import (
"errors"
"fmt"
"reflect"
)
import (
"github.com/hashicorp/go-multierror"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"k8s.io/apimachinery/pkg/runtime/schema"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/validation"
)
// Schema for a resource.
type Schema interface {
fmt.Stringer
// GroupVersionKind of the resource. This is the only way to uniquely identify a resource.
GroupVersionKind() config.GroupVersionKind
// GroupVersionResource of the resource.
GroupVersionResource() schema.GroupVersionResource
// IsClusterScoped indicates that this resource is scoped to a particular namespace within a cluster.
IsClusterScoped() bool
// Kind for this resource.
Kind() string
// Plural returns the plural form of the Kind.
Plural() string
// Group for this resource.
Group() string
// Version of this resource.
Version() string
// APIVersion is a utility that returns a k8s API version string of the form "Group/Version".
APIVersion() string
// Proto returns the protocol buffer type name for this resource.
Proto() string
// ProtoPackage returns the golang package for the protobuf resource.
ProtoPackage() string
// NewInstance returns a new instance of the protocol buffer message for this resource.
NewInstance() (config.Spec, error)
// Status returns the associated status of the schema
Status() (config.Status, error)
// StatusKind returns the Kind of the status field. If unset, the field does not support status.
StatusKind() string
StatusPackage() string
// MustNewInstance calls NewInstance and panics if an error occurs.
MustNewInstance() config.Spec
// Validate this schema.
Validate() error
// ValidateConfig validates that the given config message is of the correct type for this schema
// and that the contents are valid.
ValidateConfig(cfg config.Config) (validation.Warning, error)
// Equal is a helper function for testing equality between Schema instances. This supports comparison
// with the cmp library.
Equal(other Schema) bool
}
// Builder for a Schema.
type Builder struct {
// ClusterScoped is true for resource in cluster-level.
ClusterScoped bool
// Kind is the config proto type.
Kind string
// Plural is the type in plural.
Plural string
// Group is the config proto group.
Group string
// Version is the config proto version.
Version string
// Proto refers to the protobuf message type name corresponding to the type
Proto string
StatusProto string
// ReflectType is the type of the go struct
ReflectType reflect.Type
// StatusType is the type of the associated status.
StatusType reflect.Type
// ProtoPackage refers to the name of golang package for the protobuf message.
ProtoPackage string
// StatusPackage refers to the name of the golang status package.
StatusPackage string
// ValidateProto performs validation on protobuf messages based on this schema.
ValidateProto validation.ValidateFunc
}
// Build a Schema instance.
func (b Builder) Build() (Schema, error) {
s := b.BuildNoValidate()
// Validate the schema.
if err := s.Validate(); err != nil {
return nil, err
}
return s, nil
}
// MustBuild calls Build and panics if it fails.
func (b Builder) MustBuild() Schema {
s, err := b.Build()
if err != nil {
panic(fmt.Sprintf("MustBuild: %v", err))
}
return s
}
// BuildNoValidate builds the Schema without checking the fields.
func (b Builder) BuildNoValidate() Schema {
if b.ValidateProto == nil {
b.ValidateProto = validation.EmptyValidate
}
return &schemaImpl{
clusterScoped: b.ClusterScoped,
gvk: config.GroupVersionKind{
Group: b.Group,
Version: b.Version,
Kind: b.Kind,
},
plural: b.Plural,
apiVersion: b.Group + "/" + b.Version,
proto: b.Proto,
goPackage: b.ProtoPackage,
reflectType: b.ReflectType,
validateConfig: b.ValidateProto,
statusType: b.StatusType,
statusPackage: b.StatusPackage,
}
}
type schemaImpl struct {
clusterScoped bool
gvk config.GroupVersionKind
plural string
apiVersion string
proto string
goPackage string
validateConfig validation.ValidateFunc
reflectType reflect.Type
statusType reflect.Type
statusPackage string
}
func (s *schemaImpl) GroupVersionKind() config.GroupVersionKind {
return s.gvk
}
func (s *schemaImpl) GroupVersionResource() schema.GroupVersionResource {
return schema.GroupVersionResource{
Group: s.Group(),
Version: s.Version(),
Resource: s.Plural(),
}
}
func (s *schemaImpl) IsClusterScoped() bool {
return s.clusterScoped
}
func (s *schemaImpl) Kind() string {
return s.gvk.Kind
}
func (s *schemaImpl) Plural() string {
return s.plural
}
func (s *schemaImpl) Group() string {
return s.gvk.Group
}
func (s *schemaImpl) Version() string {
return s.gvk.Version
}
func (s *schemaImpl) APIVersion() string {
return s.apiVersion
}
func (s *schemaImpl) Proto() string {
return s.proto
}
func (s *schemaImpl) ProtoPackage() string {
return s.goPackage
}
func (s *schemaImpl) StatusPackage() string {
return s.statusPackage
}
func (s *schemaImpl) Validate() (err error) {
if !labels.IsDNS1123Label(s.Kind()) {
err = multierror.Append(err, fmt.Errorf("invalid kind: %s", s.Kind()))
}
if !labels.IsDNS1123Label(s.plural) {
err = multierror.Append(err, fmt.Errorf("invalid plural for kind %s: %s", s.Kind(), s.plural))
}
if s.reflectType == nil && getProtoMessageType(s.proto) == nil {
err = multierror.Append(err, fmt.Errorf("proto message or reflect type not found: %v", s.proto))
}
return
}
func (s *schemaImpl) String() string {
return fmt.Sprintf("[Schema](%s, %q, %s)", s.Kind(), s.goPackage, s.proto)
}
func (s *schemaImpl) NewInstance() (config.Spec, error) {
rt := s.reflectType
var instance interface{}
if rt == nil {
// Use proto
t, err := protoMessageType(protoreflect.FullName(s.proto))
if err != nil || t == nil {
return nil, errors.New("failed to find reflect type")
}
instance = t.New().Interface()
} else {
instance = reflect.New(rt).Interface()
}
p, ok := instance.(config.Spec)
if !ok {
return nil, fmt.Errorf(
"newInstance: message is not an instance of config.Spec. kind:%s, type:%v, value:%v",
s.Kind(), rt, instance)
}
return p, nil
}
func (s *schemaImpl) Status() (config.Status, error) {
statTyp := s.statusType
if statTyp == nil {
return nil, errors.New("unknown status type")
}
instance := reflect.New(statTyp).Interface()
p, ok := instance.(config.Status)
if !ok {
return nil, fmt.Errorf("status: statusType not an instance of config.Status. type: %v, value: %v", statTyp, instance)
}
return p, nil
}
func (s *schemaImpl) StatusKind() string {
if s.statusType == nil {
return ""
}
return s.statusType.Name()
}
func (s *schemaImpl) MustNewInstance() config.Spec {
p, err := s.NewInstance()
if err != nil {
panic(err)
}
return p
}
func (s *schemaImpl) ValidateConfig(cfg config.Config) (validation.Warning, error) {
return s.validateConfig(cfg)
}
func (s *schemaImpl) Equal(o Schema) bool {
return s.IsClusterScoped() == o.IsClusterScoped() &&
s.Kind() == o.Kind() &&
s.Plural() == o.Plural() &&
s.Group() == o.Group() &&
s.Version() == o.Version() &&
s.Proto() == o.Proto() &&
s.ProtoPackage() == o.ProtoPackage()
}
// FromKubernetesGVK converts a Kubernetes GVK to an Istio GVK
func FromKubernetesGVK(in *schema.GroupVersionKind) config.GroupVersionKind {
return config.GroupVersionKind{
Group: in.Group,
Version: in.Version,
Kind: in.Kind,
}
}
// getProtoMessageType returns the Go lang type of the proto with the specified name.
func getProtoMessageType(protoMessageName string) reflect.Type {
t, err := protoMessageType(protoreflect.FullName(protoMessageName))
if err != nil || t == nil {
return nil
}
t.New().Interface()
return reflect.TypeOf(t.Zero().Interface())
}
var protoMessageType = protoregistry.GlobalTypes.FindMessageByName