sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/model/resource/gvk.go (about) 1 /* 2 Copyright 2022 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 resource 18 19 import ( 20 "fmt" 21 "strings" 22 23 "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" 24 ) 25 26 const ( 27 groupRequired = "group cannot be empty if the domain is empty" 28 versionRequired = "version cannot be empty" 29 kindRequired = "kind cannot be empty" 30 ) 31 32 // GVK stores the Group - Version - Kind triplet that uniquely identifies a resource. 33 // In kubebuilder, the k8s fully qualified group is stored as Group and Domain to improve UX. 34 type GVK struct { 35 Group string `json:"group,omitempty"` 36 Domain string `json:"domain,omitempty"` 37 Version string `json:"version"` 38 Kind string `json:"kind"` 39 } 40 41 // Validate checks that the GVK is valid. 42 func (gvk GVK) Validate() error { 43 // Check if the qualified group has a valid DNS1123 subdomain value 44 if gvk.QualifiedGroup() == "" { 45 return fmt.Errorf(groupRequired) 46 } 47 if err := validation.IsDNS1123Subdomain(gvk.QualifiedGroup()); err != nil { 48 // NOTE: IsDNS1123Subdomain returns a slice of strings instead of an error, so no wrapping 49 return fmt.Errorf("either Group or Domain is invalid: %s", err) 50 } 51 52 // Check if the version follows the valid pattern 53 if gvk.Version == "" { 54 return fmt.Errorf(versionRequired) 55 } 56 if err := validation.IsDNS1035Label(gvk.Version); err != nil { 57 // NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping 58 return fmt.Errorf("invalid Version: %#v", err) 59 } 60 61 // Check if kind has a valid DNS1035 label value 62 if gvk.Kind == "" { 63 return fmt.Errorf(kindRequired) 64 } 65 if errors := validation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errors) != 0 { 66 // NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping 67 return fmt.Errorf("invalid Kind: %#v", errors) 68 } 69 70 // Require kind to start with an uppercase character 71 // NOTE: previous validation already fails for empty strings, gvk.Kind[0] will not panic 72 if string(gvk.Kind[0]) == strings.ToLower(string(gvk.Kind[0])) { 73 return fmt.Errorf("invalid Kind: must start with an uppercase character") 74 } 75 76 return nil 77 } 78 79 // QualifiedGroup returns the fully qualified group name with the available information. 80 func (gvk GVK) QualifiedGroup() string { 81 switch "" { 82 case gvk.Domain: 83 return gvk.Group 84 case gvk.Group: 85 return gvk.Domain 86 default: 87 return fmt.Sprintf("%s.%s", gvk.Group, gvk.Domain) 88 } 89 } 90 91 // IsEqualTo compares two GVK objects. 92 func (gvk GVK) IsEqualTo(other GVK) bool { 93 return gvk.Group == other.Group && 94 gvk.Domain == other.Domain && 95 gvk.Version == other.Version && 96 gvk.Kind == other.Kind 97 }