blob: 994a3e3fa815638e8563a11a1113bf643d358684 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2015 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 schema
18
19import (
20 "fmt"
21 "strings"
22)
23
24// ParseResourceArg takes the common style of string which may be either `resource.group.com` or `resource.version.group.com`
25// and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended
26// but with a knowledge of all GroupVersions, calling code can take a very good guess. If there are only two segments, then
27// `*GroupVersionResource` is nil.
28// `resource.group.com` -> `group=com, version=group, resource=resource` and `group=group.com, resource=resource`
29func ParseResourceArg(arg string) (*GroupVersionResource, GroupResource) {
30 var gvr *GroupVersionResource
31 if strings.Count(arg, ".") >= 2 {
32 s := strings.SplitN(arg, ".", 3)
33 gvr = &GroupVersionResource{Group: s[2], Version: s[1], Resource: s[0]}
34 }
35
36 return gvr, ParseGroupResource(arg)
37}
38
39// ParseKindArg takes the common style of string which may be either `Kind.group.com` or `Kind.version.group.com`
40// and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended
41// but with a knowledge of all GroupKinds, calling code can take a very good guess. If there are only two segments, then
42// `*GroupVersionResource` is nil.
43// `Kind.group.com` -> `group=com, version=group, kind=Kind` and `group=group.com, kind=Kind`
44func ParseKindArg(arg string) (*GroupVersionKind, GroupKind) {
45 var gvk *GroupVersionKind
46 if strings.Count(arg, ".") >= 2 {
47 s := strings.SplitN(arg, ".", 3)
48 gvk = &GroupVersionKind{Group: s[2], Version: s[1], Kind: s[0]}
49 }
50
51 return gvk, ParseGroupKind(arg)
52}
53
54// GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying
55// concepts during lookup stages without having partially valid types
56type GroupResource struct {
57 Group string
58 Resource string
59}
60
61func (gr GroupResource) WithVersion(version string) GroupVersionResource {
62 return GroupVersionResource{Group: gr.Group, Version: version, Resource: gr.Resource}
63}
64
65func (gr GroupResource) Empty() bool {
66 return len(gr.Group) == 0 && len(gr.Resource) == 0
67}
68
69func (gr GroupResource) String() string {
70 if len(gr.Group) == 0 {
71 return gr.Resource
72 }
73 return gr.Resource + "." + gr.Group
74}
75
76func ParseGroupKind(gk string) GroupKind {
77 i := strings.Index(gk, ".")
78 if i == -1 {
79 return GroupKind{Kind: gk}
80 }
81
82 return GroupKind{Group: gk[i+1:], Kind: gk[:i]}
83}
84
85// ParseGroupResource turns "resource.group" string into a GroupResource struct. Empty strings are allowed
86// for each field.
87func ParseGroupResource(gr string) GroupResource {
88 if i := strings.Index(gr, "."); i >= 0 {
89 return GroupResource{Group: gr[i+1:], Resource: gr[:i]}
90 }
91 return GroupResource{Resource: gr}
92}
93
94// GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion
95// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling
96type GroupVersionResource struct {
97 Group string
98 Version string
99 Resource string
100}
101
102func (gvr GroupVersionResource) Empty() bool {
103 return len(gvr.Group) == 0 && len(gvr.Version) == 0 && len(gvr.Resource) == 0
104}
105
106func (gvr GroupVersionResource) GroupResource() GroupResource {
107 return GroupResource{Group: gvr.Group, Resource: gvr.Resource}
108}
109
110func (gvr GroupVersionResource) GroupVersion() GroupVersion {
111 return GroupVersion{Group: gvr.Group, Version: gvr.Version}
112}
113
114func (gvr GroupVersionResource) String() string {
115 return strings.Join([]string{gvr.Group, "/", gvr.Version, ", Resource=", gvr.Resource}, "")
116}
117
118// GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying
119// concepts during lookup stages without having partially valid types
120type GroupKind struct {
121 Group string
122 Kind string
123}
124
125func (gk GroupKind) Empty() bool {
126 return len(gk.Group) == 0 && len(gk.Kind) == 0
127}
128
129func (gk GroupKind) WithVersion(version string) GroupVersionKind {
130 return GroupVersionKind{Group: gk.Group, Version: version, Kind: gk.Kind}
131}
132
133func (gk GroupKind) String() string {
134 if len(gk.Group) == 0 {
135 return gk.Kind
136 }
137 return gk.Kind + "." + gk.Group
138}
139
140// GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion
141// to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling
142type GroupVersionKind struct {
143 Group string
144 Version string
145 Kind string
146}
147
148// Empty returns true if group, version, and kind are empty
149func (gvk GroupVersionKind) Empty() bool {
150 return len(gvk.Group) == 0 && len(gvk.Version) == 0 && len(gvk.Kind) == 0
151}
152
153func (gvk GroupVersionKind) GroupKind() GroupKind {
154 return GroupKind{Group: gvk.Group, Kind: gvk.Kind}
155}
156
157func (gvk GroupVersionKind) GroupVersion() GroupVersion {
158 return GroupVersion{Group: gvk.Group, Version: gvk.Version}
159}
160
161func (gvk GroupVersionKind) String() string {
162 return gvk.Group + "/" + gvk.Version + ", Kind=" + gvk.Kind
163}
164
165// GroupVersion contains the "group" and the "version", which uniquely identifies the API.
166type GroupVersion struct {
167 Group string
168 Version string
169}
170
171// Empty returns true if group and version are empty
172func (gv GroupVersion) Empty() bool {
173 return len(gv.Group) == 0 && len(gv.Version) == 0
174}
175
176// String puts "group" and "version" into a single "group/version" string. For the legacy v1
177// it returns "v1".
178func (gv GroupVersion) String() string {
179 if len(gv.Group) > 0 {
180 return gv.Group + "/" + gv.Version
181 }
182 return gv.Version
183}
184
185// Identifier implements runtime.GroupVersioner interface.
186func (gv GroupVersion) Identifier() string {
187 return gv.String()
188}
189
190// KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false
191// if none of the options match the group. It prefers a match to group and version over just group.
192// TODO: Move GroupVersion to a package under pkg/runtime, since it's used by scheme.
193// TODO: Introduce an adapter type between GroupVersion and runtime.GroupVersioner, and use LegacyCodec(GroupVersion)
194// in fewer places.
195func (gv GroupVersion) KindForGroupVersionKinds(kinds []GroupVersionKind) (target GroupVersionKind, ok bool) {
196 for _, gvk := range kinds {
197 if gvk.Group == gv.Group && gvk.Version == gv.Version {
198 return gvk, true
199 }
200 }
201 for _, gvk := range kinds {
202 if gvk.Group == gv.Group {
203 return gv.WithKind(gvk.Kind), true
204 }
205 }
206 return GroupVersionKind{}, false
207}
208
209// ParseGroupVersion turns "group/version" string into a GroupVersion struct. It reports error
210// if it cannot parse the string.
211func ParseGroupVersion(gv string) (GroupVersion, error) {
212 // this can be the internal version for the legacy kube types
213 // TODO once we've cleared the last uses as strings, this special case should be removed.
214 if (len(gv) == 0) || (gv == "/") {
215 return GroupVersion{}, nil
216 }
217
218 switch strings.Count(gv, "/") {
219 case 0:
220 return GroupVersion{"", gv}, nil
221 case 1:
222 i := strings.Index(gv, "/")
223 return GroupVersion{gv[:i], gv[i+1:]}, nil
224 default:
225 return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv)
226 }
227}
228
229// WithKind creates a GroupVersionKind based on the method receiver's GroupVersion and the passed Kind.
230func (gv GroupVersion) WithKind(kind string) GroupVersionKind {
231 return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind}
232}
233
234// WithResource creates a GroupVersionResource based on the method receiver's GroupVersion and the passed Resource.
235func (gv GroupVersion) WithResource(resource string) GroupVersionResource {
236 return GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: resource}
237}
238
239// GroupVersions can be used to represent a set of desired group versions.
240// TODO: Move GroupVersions to a package under pkg/runtime, since it's used by scheme.
241// TODO: Introduce an adapter type between GroupVersions and runtime.GroupVersioner, and use LegacyCodec(GroupVersion)
242// in fewer places.
243type GroupVersions []GroupVersion
244
245// Identifier implements runtime.GroupVersioner interface.
246func (gvs GroupVersions) Identifier() string {
247 groupVersions := make([]string, 0, len(gvs))
248 for i := range gvs {
249 groupVersions = append(groupVersions, gvs[i].String())
250 }
251 return fmt.Sprintf("[%s]", strings.Join(groupVersions, ","))
252}
253
254// KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false
255// if none of the options match the group.
256func (gvs GroupVersions) KindForGroupVersionKinds(kinds []GroupVersionKind) (GroupVersionKind, bool) {
257 var targets []GroupVersionKind
258 for _, gv := range gvs {
259 target, ok := gv.KindForGroupVersionKinds(kinds)
260 if !ok {
261 continue
262 }
263 targets = append(targets, target)
264 }
265 if len(targets) == 1 {
266 return targets[0], true
267 }
268 if len(targets) > 1 {
269 return bestMatch(kinds, targets), true
270 }
271 return GroupVersionKind{}, false
272}
273
274// bestMatch tries to pick best matching GroupVersionKind and falls back to the first
275// found if no exact match exists.
276func bestMatch(kinds []GroupVersionKind, targets []GroupVersionKind) GroupVersionKind {
277 for _, gvk := range targets {
278 for _, k := range kinds {
279 if k == gvk {
280 return k
281 }
282 }
283 }
284 return targets[0]
285}
286
287// ToAPIVersionAndKind is a convenience method for satisfying runtime.Object on types that
288// do not use TypeMeta.
289func (gvk GroupVersionKind) ToAPIVersionAndKind() (string, string) {
290 if gvk.Empty() {
291 return "", ""
292 }
293 return gvk.GroupVersion().String(), gvk.Kind
294}
295
296// FromAPIVersionAndKind returns a GVK representing the provided fields for types that
297// do not use TypeMeta. This method exists to support test types and legacy serializations
298// that have a distinct group and kind.
299// TODO: further reduce usage of this method.
300func FromAPIVersionAndKind(apiVersion, kind string) GroupVersionKind {
301 if gv, err := ParseGroupVersion(apiVersion); err == nil {
302 return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind}
303 }
304 return GroupVersionKind{Kind: kind}
305}