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  }