Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2015 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package schema |
| 18 | |
| 19 | import ( |
| 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` |
| 29 | func 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` |
| 44 | func 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 |
| 56 | type GroupResource struct { |
| 57 | Group string |
| 58 | Resource string |
| 59 | } |
| 60 | |
| 61 | func (gr GroupResource) WithVersion(version string) GroupVersionResource { |
| 62 | return GroupVersionResource{Group: gr.Group, Version: version, Resource: gr.Resource} |
| 63 | } |
| 64 | |
| 65 | func (gr GroupResource) Empty() bool { |
| 66 | return len(gr.Group) == 0 && len(gr.Resource) == 0 |
| 67 | } |
| 68 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 69 | func (gr GroupResource) String() string { |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 70 | if len(gr.Group) == 0 { |
| 71 | return gr.Resource |
| 72 | } |
| 73 | return gr.Resource + "." + gr.Group |
| 74 | } |
| 75 | |
| 76 | func 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. |
| 87 | func 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 |
| 96 | type GroupVersionResource struct { |
| 97 | Group string |
| 98 | Version string |
| 99 | Resource string |
| 100 | } |
| 101 | |
| 102 | func (gvr GroupVersionResource) Empty() bool { |
| 103 | return len(gvr.Group) == 0 && len(gvr.Version) == 0 && len(gvr.Resource) == 0 |
| 104 | } |
| 105 | |
| 106 | func (gvr GroupVersionResource) GroupResource() GroupResource { |
| 107 | return GroupResource{Group: gvr.Group, Resource: gvr.Resource} |
| 108 | } |
| 109 | |
| 110 | func (gvr GroupVersionResource) GroupVersion() GroupVersion { |
| 111 | return GroupVersion{Group: gvr.Group, Version: gvr.Version} |
| 112 | } |
| 113 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 114 | func (gvr GroupVersionResource) String() string { |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 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 |
| 120 | type GroupKind struct { |
| 121 | Group string |
| 122 | Kind string |
| 123 | } |
| 124 | |
| 125 | func (gk GroupKind) Empty() bool { |
| 126 | return len(gk.Group) == 0 && len(gk.Kind) == 0 |
| 127 | } |
| 128 | |
| 129 | func (gk GroupKind) WithVersion(version string) GroupVersionKind { |
| 130 | return GroupVersionKind{Group: gk.Group, Version: version, Kind: gk.Kind} |
| 131 | } |
| 132 | |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 133 | func (gk GroupKind) String() string { |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 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 |
| 142 | type GroupVersionKind struct { |
| 143 | Group string |
| 144 | Version string |
| 145 | Kind string |
| 146 | } |
| 147 | |
| 148 | // Empty returns true if group, version, and kind are empty |
| 149 | func (gvk GroupVersionKind) Empty() bool { |
| 150 | return len(gvk.Group) == 0 && len(gvk.Version) == 0 && len(gvk.Kind) == 0 |
| 151 | } |
| 152 | |
| 153 | func (gvk GroupVersionKind) GroupKind() GroupKind { |
| 154 | return GroupKind{Group: gvk.Group, Kind: gvk.Kind} |
| 155 | } |
| 156 | |
| 157 | func (gvk GroupVersionKind) GroupVersion() GroupVersion { |
| 158 | return GroupVersion{Group: gvk.Group, Version: gvk.Version} |
| 159 | } |
| 160 | |
| 161 | func (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. |
| 166 | type GroupVersion struct { |
| 167 | Group string |
| 168 | Version string |
| 169 | } |
| 170 | |
| 171 | // Empty returns true if group and version are empty |
| 172 | func (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". |
| 178 | func (gv GroupVersion) String() string { |
| 179 | // special case the internal apiVersion for the legacy kube types |
| 180 | if gv.Empty() { |
| 181 | return "" |
| 182 | } |
| 183 | |
| 184 | // special case of "v1" for backward compatibility |
| 185 | if len(gv.Group) == 0 && gv.Version == "v1" { |
| 186 | return gv.Version |
| 187 | } |
| 188 | if len(gv.Group) > 0 { |
| 189 | return gv.Group + "/" + gv.Version |
| 190 | } |
| 191 | return gv.Version |
| 192 | } |
| 193 | |
| 194 | // KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false |
| 195 | // if none of the options match the group. It prefers a match to group and version over just group. |
| 196 | // TODO: Move GroupVersion to a package under pkg/runtime, since it's used by scheme. |
| 197 | // TODO: Introduce an adapter type between GroupVersion and runtime.GroupVersioner, and use LegacyCodec(GroupVersion) |
| 198 | // in fewer places. |
| 199 | func (gv GroupVersion) KindForGroupVersionKinds(kinds []GroupVersionKind) (target GroupVersionKind, ok bool) { |
| 200 | for _, gvk := range kinds { |
| 201 | if gvk.Group == gv.Group && gvk.Version == gv.Version { |
| 202 | return gvk, true |
| 203 | } |
| 204 | } |
| 205 | for _, gvk := range kinds { |
| 206 | if gvk.Group == gv.Group { |
| 207 | return gv.WithKind(gvk.Kind), true |
| 208 | } |
| 209 | } |
| 210 | return GroupVersionKind{}, false |
| 211 | } |
| 212 | |
| 213 | // ParseGroupVersion turns "group/version" string into a GroupVersion struct. It reports error |
| 214 | // if it cannot parse the string. |
| 215 | func ParseGroupVersion(gv string) (GroupVersion, error) { |
| 216 | // this can be the internal version for the legacy kube types |
| 217 | // TODO once we've cleared the last uses as strings, this special case should be removed. |
| 218 | if (len(gv) == 0) || (gv == "/") { |
| 219 | return GroupVersion{}, nil |
| 220 | } |
| 221 | |
| 222 | switch strings.Count(gv, "/") { |
| 223 | case 0: |
| 224 | return GroupVersion{"", gv}, nil |
| 225 | case 1: |
| 226 | i := strings.Index(gv, "/") |
| 227 | return GroupVersion{gv[:i], gv[i+1:]}, nil |
| 228 | default: |
| 229 | return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // WithKind creates a GroupVersionKind based on the method receiver's GroupVersion and the passed Kind. |
| 234 | func (gv GroupVersion) WithKind(kind string) GroupVersionKind { |
| 235 | return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} |
| 236 | } |
| 237 | |
| 238 | // WithResource creates a GroupVersionResource based on the method receiver's GroupVersion and the passed Resource. |
| 239 | func (gv GroupVersion) WithResource(resource string) GroupVersionResource { |
| 240 | return GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: resource} |
| 241 | } |
| 242 | |
| 243 | // GroupVersions can be used to represent a set of desired group versions. |
| 244 | // TODO: Move GroupVersions to a package under pkg/runtime, since it's used by scheme. |
| 245 | // TODO: Introduce an adapter type between GroupVersions and runtime.GroupVersioner, and use LegacyCodec(GroupVersion) |
| 246 | // in fewer places. |
| 247 | type GroupVersions []GroupVersion |
| 248 | |
| 249 | // KindForGroupVersionKinds identifies the preferred GroupVersionKind out of a list. It returns ok false |
| 250 | // if none of the options match the group. |
| 251 | func (gvs GroupVersions) KindForGroupVersionKinds(kinds []GroupVersionKind) (GroupVersionKind, bool) { |
| 252 | var targets []GroupVersionKind |
| 253 | for _, gv := range gvs { |
| 254 | target, ok := gv.KindForGroupVersionKinds(kinds) |
| 255 | if !ok { |
| 256 | continue |
| 257 | } |
| 258 | targets = append(targets, target) |
| 259 | } |
| 260 | if len(targets) == 1 { |
| 261 | return targets[0], true |
| 262 | } |
| 263 | if len(targets) > 1 { |
| 264 | return bestMatch(kinds, targets), true |
| 265 | } |
| 266 | return GroupVersionKind{}, false |
| 267 | } |
| 268 | |
| 269 | // bestMatch tries to pick best matching GroupVersionKind and falls back to the first |
| 270 | // found if no exact match exists. |
| 271 | func bestMatch(kinds []GroupVersionKind, targets []GroupVersionKind) GroupVersionKind { |
| 272 | for _, gvk := range targets { |
| 273 | for _, k := range kinds { |
| 274 | if k == gvk { |
| 275 | return k |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | return targets[0] |
| 280 | } |
| 281 | |
| 282 | // ToAPIVersionAndKind is a convenience method for satisfying runtime.Object on types that |
| 283 | // do not use TypeMeta. |
David Bainbridge | 8697152 | 2019-09-26 22:09:39 +0000 | [diff] [blame] | 284 | func (gvk GroupVersionKind) ToAPIVersionAndKind() (string, string) { |
| 285 | if gvk.Empty() { |
Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 286 | return "", "" |
| 287 | } |
| 288 | return gvk.GroupVersion().String(), gvk.Kind |
| 289 | } |
| 290 | |
| 291 | // FromAPIVersionAndKind returns a GVK representing the provided fields for types that |
| 292 | // do not use TypeMeta. This method exists to support test types and legacy serializations |
| 293 | // that have a distinct group and kind. |
| 294 | // TODO: further reduce usage of this method. |
| 295 | func FromAPIVersionAndKind(apiVersion, kind string) GroupVersionKind { |
| 296 | if gv, err := ParseGroupVersion(apiVersion); err == nil { |
| 297 | return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} |
| 298 | } |
| 299 | return GroupVersionKind{Kind: kind} |
| 300 | } |