1// Copyright 2019 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
15package collection
16
17import (
18	"fmt"
19	"sort"
20	"strings"
21
22	"github.com/google/go-cmp/cmp"
23	"github.com/hashicorp/go-multierror"
24
25	"istio.io/istio/pkg/config/schema/resource"
26)
27
28// Schemas contains metadata about configuration resources.
29type Schemas struct {
30	byCollection map[Name]Schema
31	byAddOrder   []Schema
32}
33
34// SchemasFor is a shortcut for creating Schemas. It uses MustAdd for each element.
35func SchemasFor(schemas ...Schema) Schemas {
36	b := NewSchemasBuilder()
37	for _, s := range schemas {
38		b.MustAdd(s)
39	}
40	return b.Build()
41}
42
43// SchemasBuilder is a builder for the schemas type.
44type SchemasBuilder struct {
45	schemas Schemas
46}
47
48// NewSchemasBuilder returns a new instance of SchemasBuilder.
49func NewSchemasBuilder() *SchemasBuilder {
50	s := Schemas{
51		byCollection: make(map[Name]Schema),
52	}
53
54	return &SchemasBuilder{
55		schemas: s,
56	}
57}
58
59// Add a new collection to the schemas.
60func (b *SchemasBuilder) Add(s Schema) error {
61	if _, found := b.schemas.byCollection[s.Name()]; found {
62		return fmt.Errorf("collection already exists: %v", s.Name())
63	}
64
65	b.schemas.byCollection[s.Name()] = s
66	b.schemas.byAddOrder = append(b.schemas.byAddOrder, s)
67	return nil
68}
69
70// MustAdd calls Add and panics if it fails.
71func (b *SchemasBuilder) MustAdd(s Schema) *SchemasBuilder {
72	if err := b.Add(s); err != nil {
73		panic(fmt.Sprintf("SchemasBuilder.MustAdd: %v", err))
74	}
75	return b
76}
77
78// Build a new schemas from this SchemasBuilder.
79func (b *SchemasBuilder) Build() Schemas {
80	s := b.schemas
81
82	// Avoid modify after Build.
83	b.schemas = Schemas{}
84
85	return s
86}
87
88// ForEach executes the given function on each contained schema, until the function returns true.
89func (s Schemas) ForEach(handleSchema func(Schema) (done bool)) {
90	for _, schema := range s.byAddOrder {
91		if handleSchema(schema) {
92			return
93		}
94	}
95}
96
97// Find looks up a Schema by its collection name.
98func (s Schemas) Find(collection string) (Schema, bool) {
99	i, ok := s.byCollection[Name(collection)]
100	return i, ok
101}
102
103// MustFind calls Find and panics if not found.
104func (s Schemas) MustFind(collection string) Schema {
105	i, ok := s.Find(collection)
106	if !ok {
107		panic(fmt.Sprintf("schemas.MustFind: matching entry not found for collection: %q", collection))
108	}
109	return i
110}
111
112// FindByKind searches and returns the first schema with the given kind
113func (s Schemas) FindByGroupVersionKind(gvk resource.GroupVersionKind) (Schema, bool) {
114	for _, rs := range s.byAddOrder {
115		if rs.Resource().GroupVersionKind() == gvk {
116			return rs, true
117		}
118	}
119
120	return nil, false
121}
122
123// FindByKind searches and returns the first schema with the given kind
124func (s Schemas) FindByPlural(plural string) (Schema, bool) {
125	for _, rs := range s.byAddOrder {
126		if rs.Resource().Plural() == plural {
127			return rs, true
128		}
129	}
130
131	return nil, false
132}
133
134// MustFind calls FindByGroupVersionKind and panics if not found.
135func (s Schemas) MustFindByGroupVersionKind(gvk resource.GroupVersionKind) Schema {
136	r, found := s.FindByGroupVersionKind(gvk)
137	if !found {
138		panic(fmt.Sprintf("Schemas.MustFindByGroupVersionKind: unable to find %s", gvk))
139	}
140	return r
141}
142
143// All returns all known Schemas
144func (s Schemas) All() []Schema {
145	return append(make([]Schema, 0, len(s.byAddOrder)), s.byAddOrder...)
146}
147
148// Add creates a copy of this Schemas with the given schemas added.
149func (s Schemas) Add(toAdd ...Schema) Schemas {
150	b := NewSchemasBuilder()
151
152	for _, s := range s.byAddOrder {
153		b.MustAdd(s)
154	}
155
156	for _, s := range toAdd {
157		b.MustAdd(s)
158	}
159
160	return b.Build()
161
162}
163
164// Remove creates a copy of this Schemas with the given schemas removed.
165func (s Schemas) Remove(toRemove ...Schema) Schemas {
166	b := NewSchemasBuilder()
167
168	for _, s := range s.byAddOrder {
169		shouldAdd := true
170		for _, r := range toRemove {
171			if r.Name() == s.Name() {
172				shouldAdd = false
173				break
174			}
175		}
176		if shouldAdd {
177			b.MustAdd(s)
178		}
179	}
180
181	return b.Build()
182}
183
184// CollectionNames returns all known collections.
185func (s Schemas) CollectionNames() Names {
186	result := make(Names, 0, len(s.byAddOrder))
187
188	for _, info := range s.byAddOrder {
189		result = append(result, info.Name())
190	}
191
192	sort.Slice(result, func(i, j int) bool {
193		return strings.Compare(result[i].String(), result[j].String()) < 0
194	})
195
196	return result
197}
198
199// Kinds returns all known resource kinds.
200func (s Schemas) Kinds() []string {
201	kinds := make(map[string]struct{}, len(s.byAddOrder))
202	for _, s := range s.byAddOrder {
203		kinds[s.Resource().Kind()] = struct{}{}
204	}
205
206	out := make([]string, 0, len(kinds))
207	for kind := range kinds {
208		out = append(out, kind)
209	}
210
211	sort.Strings(out)
212	return out
213}
214
215// DisabledCollectionNames returns the names of disabled collections
216func (s Schemas) DisabledCollectionNames() Names {
217	disabledCollections := make(Names, 0)
218	for _, i := range s.byAddOrder {
219		if i.IsDisabled() {
220			disabledCollections = append(disabledCollections, i.Name())
221		}
222	}
223	return disabledCollections
224}
225
226// Validate the schemas. Returns error if there is a problem.
227func (s Schemas) Validate() (err error) {
228	for _, c := range s.byAddOrder {
229		err = multierror.Append(err, c.Resource().Validate()).ErrorOrNil()
230	}
231	return
232}
233
234func (s Schemas) Equal(o Schemas) bool {
235	return cmp.Equal(s.byAddOrder, o.byAddOrder)
236}
237