blob: fa11c580f70034ab42429c984ac06755960fe547 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package meta
18
19import (
20 "fmt"
21
22 "k8s.io/apimachinery/pkg/runtime/schema"
23)
24
25const (
26 AnyGroup = "*"
27 AnyVersion = "*"
28 AnyResource = "*"
29 AnyKind = "*"
30)
31
32// PriorityRESTMapper is a wrapper for automatically choosing a particular Resource or Kind
33// when multiple matches are possible
34type PriorityRESTMapper struct {
35 // Delegate is the RESTMapper to use to locate all the Kind and Resource matches
36 Delegate RESTMapper
37
38 // ResourcePriority is a list of priority patterns to apply to matching resources.
39 // The list of all matching resources is narrowed based on the patterns until only one remains.
40 // A pattern with no matches is skipped. A pattern with more than one match uses its
41 // matches as the list to continue matching against.
42 ResourcePriority []schema.GroupVersionResource
43
44 // KindPriority is a list of priority patterns to apply to matching kinds.
45 // The list of all matching kinds is narrowed based on the patterns until only one remains.
46 // A pattern with no matches is skipped. A pattern with more than one match uses its
47 // matches as the list to continue matching against.
48 KindPriority []schema.GroupVersionKind
49}
50
51func (m PriorityRESTMapper) String() string {
52 return fmt.Sprintf("PriorityRESTMapper{\n\t%v\n\t%v\n\t%v\n}", m.ResourcePriority, m.KindPriority, m.Delegate)
53}
54
55// ResourceFor finds all resources, then passes them through the ResourcePriority patterns to find a single matching hit.
56func (m PriorityRESTMapper) ResourceFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
57 originalGVRs, originalErr := m.Delegate.ResourcesFor(partiallySpecifiedResource)
58 if originalErr != nil && len(originalGVRs) == 0 {
59 return schema.GroupVersionResource{}, originalErr
60 }
61 if len(originalGVRs) == 1 {
62 return originalGVRs[0], originalErr
63 }
64
65 remainingGVRs := append([]schema.GroupVersionResource{}, originalGVRs...)
66 for _, pattern := range m.ResourcePriority {
67 matchedGVRs := []schema.GroupVersionResource{}
68 for _, gvr := range remainingGVRs {
69 if resourceMatches(pattern, gvr) {
70 matchedGVRs = append(matchedGVRs, gvr)
71 }
72 }
73
74 switch len(matchedGVRs) {
75 case 0:
76 // if you have no matches, then nothing matched this pattern just move to the next
77 continue
78 case 1:
79 // one match, return
80 return matchedGVRs[0], originalErr
81 default:
82 // more than one match, use the matched hits as the list moving to the next pattern.
83 // this way you can have a series of selection criteria
84 remainingGVRs = matchedGVRs
85 }
86 }
87
88 return schema.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingResources: originalGVRs}
89}
90
91// KindFor finds all kinds, then passes them through the KindPriority patterns to find a single matching hit.
92func (m PriorityRESTMapper) KindFor(partiallySpecifiedResource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
93 originalGVKs, originalErr := m.Delegate.KindsFor(partiallySpecifiedResource)
94 if originalErr != nil && len(originalGVKs) == 0 {
95 return schema.GroupVersionKind{}, originalErr
96 }
97 if len(originalGVKs) == 1 {
98 return originalGVKs[0], originalErr
99 }
100
101 remainingGVKs := append([]schema.GroupVersionKind{}, originalGVKs...)
102 for _, pattern := range m.KindPriority {
103 matchedGVKs := []schema.GroupVersionKind{}
104 for _, gvr := range remainingGVKs {
105 if kindMatches(pattern, gvr) {
106 matchedGVKs = append(matchedGVKs, gvr)
107 }
108 }
109
110 switch len(matchedGVKs) {
111 case 0:
112 // if you have no matches, then nothing matched this pattern just move to the next
113 continue
114 case 1:
115 // one match, return
116 return matchedGVKs[0], originalErr
117 default:
118 // more than one match, use the matched hits as the list moving to the next pattern.
119 // this way you can have a series of selection criteria
120 remainingGVKs = matchedGVKs
121 }
122 }
123
124 return schema.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingKinds: originalGVKs}
125}
126
127func resourceMatches(pattern schema.GroupVersionResource, resource schema.GroupVersionResource) bool {
128 if pattern.Group != AnyGroup && pattern.Group != resource.Group {
129 return false
130 }
131 if pattern.Version != AnyVersion && pattern.Version != resource.Version {
132 return false
133 }
134 if pattern.Resource != AnyResource && pattern.Resource != resource.Resource {
135 return false
136 }
137
138 return true
139}
140
141func kindMatches(pattern schema.GroupVersionKind, kind schema.GroupVersionKind) bool {
142 if pattern.Group != AnyGroup && pattern.Group != kind.Group {
143 return false
144 }
145 if pattern.Version != AnyVersion && pattern.Version != kind.Version {
146 return false
147 }
148 if pattern.Kind != AnyKind && pattern.Kind != kind.Kind {
149 return false
150 }
151
152 return true
153}
154
155func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) {
156 mappings, originalErr := m.Delegate.RESTMappings(gk, versions...)
157 if originalErr != nil && len(mappings) == 0 {
158 return nil, originalErr
159 }
160
161 // any versions the user provides take priority
162 priorities := m.KindPriority
163 if len(versions) > 0 {
164 priorities = make([]schema.GroupVersionKind, 0, len(m.KindPriority)+len(versions))
165 for _, version := range versions {
166 gv := schema.GroupVersion{
167 Version: version,
168 Group: gk.Group,
169 }
170 priorities = append(priorities, gv.WithKind(AnyKind))
171 }
172 priorities = append(priorities, m.KindPriority...)
173 }
174
175 remaining := append([]*RESTMapping{}, mappings...)
176 for _, pattern := range priorities {
177 var matching []*RESTMapping
178 for _, m := range remaining {
179 if kindMatches(pattern, m.GroupVersionKind) {
180 matching = append(matching, m)
181 }
182 }
183
184 switch len(matching) {
185 case 0:
186 // if you have no matches, then nothing matched this pattern just move to the next
187 continue
188 case 1:
189 // one match, return
190 return matching[0], originalErr
191 default:
192 // more than one match, use the matched hits as the list moving to the next pattern.
193 // this way you can have a series of selection criteria
194 remaining = matching
195 }
196 }
197 if len(remaining) == 1 {
198 return remaining[0], originalErr
199 }
200
201 var kinds []schema.GroupVersionKind
202 for _, m := range mappings {
203 kinds = append(kinds, m.GroupVersionKind)
204 }
205 return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds}
206}
207
208func (m PriorityRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error) {
209 return m.Delegate.RESTMappings(gk, versions...)
210}
211
212func (m PriorityRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
213 return m.Delegate.ResourceSingularizer(resource)
214}
215
216func (m PriorityRESTMapper) ResourcesFor(partiallySpecifiedResource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
217 return m.Delegate.ResourcesFor(partiallySpecifiedResource)
218}
219
220func (m PriorityRESTMapper) KindsFor(partiallySpecifiedResource schema.GroupVersionResource) (gvk []schema.GroupVersionKind, err error) {
221 return m.Delegate.KindsFor(partiallySpecifiedResource)
222}