istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/schema/collection/schemas.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 collection
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/google/go-cmp/cmp"
    21  	"github.com/hashicorp/go-multierror"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  
    24  	"istio.io/istio/pkg/config"
    25  	"istio.io/istio/pkg/config/schema/resource"
    26  	"istio.io/istio/pkg/slices"
    27  	"istio.io/istio/pkg/util/sets"
    28  )
    29  
    30  // Schemas contains metadata about configuration resources.
    31  type Schemas struct {
    32  	byCollection map[config.GroupVersionKind]resource.Schema
    33  	byAddOrder   []resource.Schema
    34  }
    35  
    36  // SchemasFor is a shortcut for creating Schemas. It uses MustAdd for each element.
    37  func SchemasFor(schemas ...resource.Schema) Schemas {
    38  	b := NewSchemasBuilder()
    39  	for _, s := range schemas {
    40  		b.MustAdd(s)
    41  	}
    42  	return b.Build()
    43  }
    44  
    45  // SchemasBuilder is a builder for the schemas type.
    46  type SchemasBuilder struct {
    47  	schemas Schemas
    48  }
    49  
    50  // NewSchemasBuilder returns a new instance of SchemasBuilder.
    51  func NewSchemasBuilder() *SchemasBuilder {
    52  	s := Schemas{
    53  		byCollection: make(map[config.GroupVersionKind]resource.Schema),
    54  	}
    55  
    56  	return &SchemasBuilder{
    57  		schemas: s,
    58  	}
    59  }
    60  
    61  // Add a new collection to the schemas.
    62  func (b *SchemasBuilder) Add(s resource.Schema) error {
    63  	if _, found := b.schemas.byCollection[s.GroupVersionKind()]; found {
    64  		return fmt.Errorf("collection already exists: %v", s.GroupVersionKind())
    65  	}
    66  
    67  	b.schemas.byCollection[s.GroupVersionKind()] = s
    68  	b.schemas.byAddOrder = append(b.schemas.byAddOrder, s)
    69  	return nil
    70  }
    71  
    72  // MustAdd calls Add and panics if it fails.
    73  func (b *SchemasBuilder) MustAdd(s resource.Schema) *SchemasBuilder {
    74  	if err := b.Add(s); err != nil {
    75  		panic(fmt.Sprintf("SchemasBuilder.MustAdd: %v", err))
    76  	}
    77  	return b
    78  }
    79  
    80  // Build a new schemas from this SchemasBuilder.
    81  func (b *SchemasBuilder) Build() Schemas {
    82  	s := b.schemas
    83  
    84  	// Avoid modify after Build.
    85  	b.schemas = Schemas{}
    86  
    87  	return s
    88  }
    89  
    90  // ForEach executes the given function on each contained schema, until the function returns true.
    91  func (s Schemas) ForEach(handleSchema func(resource.Schema) (done bool)) {
    92  	for _, schema := range s.byAddOrder {
    93  		if handleSchema(schema) {
    94  			return
    95  		}
    96  	}
    97  }
    98  
    99  func (s Schemas) Union(otherSchemas Schemas) Schemas {
   100  	resultBuilder := NewSchemasBuilder()
   101  	for _, myschema := range s.All() {
   102  		// an error indicates the schema has already been added, which doesn't negatively impact intersect
   103  		_ = resultBuilder.Add(myschema)
   104  	}
   105  	for _, myschema := range otherSchemas.All() {
   106  		// an error indicates the schema has already been added, which doesn't negatively impact intersect
   107  		_ = resultBuilder.Add(myschema)
   108  	}
   109  	return resultBuilder.Build()
   110  }
   111  
   112  func (s Schemas) Intersect(otherSchemas Schemas) Schemas {
   113  	resultBuilder := NewSchemasBuilder()
   114  
   115  	schemaLookup := sets.String{}
   116  	for _, myschema := range s.All() {
   117  		schemaLookup.Insert(myschema.String())
   118  	}
   119  
   120  	// Only add schemas that are in both sets
   121  	for _, myschema := range otherSchemas.All() {
   122  		if schemaLookup.Contains(myschema.String()) {
   123  			_ = resultBuilder.Add(myschema)
   124  		}
   125  	}
   126  	return resultBuilder.Build()
   127  }
   128  
   129  // FindByGroupVersionKind searches and returns the first schema with the given GVK
   130  func (s Schemas) FindByGroupVersionKind(gvk config.GroupVersionKind) (resource.Schema, bool) {
   131  	for _, rs := range s.byAddOrder {
   132  		if rs.GroupVersionKind() == gvk {
   133  			return rs, true
   134  		}
   135  	}
   136  
   137  	return nil, false
   138  }
   139  
   140  // FindByGroupVersionAliasesKind searches and returns the first schema with the given GVK,
   141  // if not found, it will search for version aliases for the schema to see if there is a match.
   142  func (s Schemas) FindByGroupVersionAliasesKind(gvk config.GroupVersionKind) (resource.Schema, bool) {
   143  	for _, rs := range s.byAddOrder {
   144  		for _, va := range rs.GroupVersionAliasKinds() {
   145  			if va == gvk {
   146  				return rs, true
   147  			}
   148  		}
   149  	}
   150  	return nil, false
   151  }
   152  
   153  // FindByGroupVersionResource searches and returns the first schema with the given GVR
   154  func (s Schemas) FindByGroupVersionResource(gvr schema.GroupVersionResource) (resource.Schema, bool) {
   155  	for _, rs := range s.byAddOrder {
   156  		if rs.GroupVersionResource() == gvr {
   157  			return rs, true
   158  		}
   159  	}
   160  
   161  	return nil, false
   162  }
   163  
   164  // All returns all known Schemas
   165  func (s Schemas) All() []resource.Schema {
   166  	return slices.Clone(s.byAddOrder)
   167  }
   168  
   169  // GroupVersionKinds returns all known GroupVersionKinds
   170  func (s Schemas) GroupVersionKinds() []config.GroupVersionKind {
   171  	res := []config.GroupVersionKind{}
   172  	for _, r := range s.All() {
   173  		res = append(res, r.GroupVersionKind())
   174  	}
   175  	return res
   176  }
   177  
   178  // Add creates a copy of this Schemas with the given schemas added.
   179  func (s Schemas) Add(toAdd ...resource.Schema) Schemas {
   180  	b := NewSchemasBuilder()
   181  
   182  	for _, s := range s.byAddOrder {
   183  		b.MustAdd(s)
   184  	}
   185  
   186  	for _, s := range toAdd {
   187  		b.MustAdd(s)
   188  	}
   189  
   190  	return b.Build()
   191  }
   192  
   193  // Remove creates a copy of this Schemas with the given schemas removed.
   194  func (s Schemas) Remove(toRemove ...resource.Schema) Schemas {
   195  	b := NewSchemasBuilder()
   196  
   197  	for _, s := range s.byAddOrder {
   198  		shouldAdd := true
   199  		for _, r := range toRemove {
   200  			if r.Equal(s) {
   201  				shouldAdd = false
   202  				break
   203  			}
   204  		}
   205  		if shouldAdd {
   206  			b.MustAdd(s)
   207  		}
   208  	}
   209  
   210  	return b.Build()
   211  }
   212  
   213  // Kinds returns all known resource kinds.
   214  func (s Schemas) Kinds() []string {
   215  	kinds := sets.NewWithLength[string](len(s.byAddOrder))
   216  	for _, s := range s.byAddOrder {
   217  		kinds.Insert(s.Kind())
   218  	}
   219  
   220  	out := kinds.UnsortedList()
   221  	return slices.Sort(out)
   222  }
   223  
   224  // Validate the schemas. Returns error if there is a problem.
   225  func (s Schemas) Validate() (err error) {
   226  	for _, c := range s.byAddOrder {
   227  		err = multierror.Append(err, c.Validate()).ErrorOrNil()
   228  	}
   229  	return
   230  }
   231  
   232  func (s Schemas) Equal(o Schemas) bool {
   233  	return cmp.Equal(s.byAddOrder, o.byAddOrder)
   234  }