...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdloader/crdloader.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/crd/crdloader

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package crdloader
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"path"
    22  	"strings"
    23  
    24  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
    25  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
    26  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util/repo"
    27  
    28  	"github.com/ghodss/yaml"
    29  	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  )
    35  
    36  type CrdLoader struct {
    37  	kubeClient client.Client
    38  }
    39  
    40  func New(kubeClient client.Client) *CrdLoader {
    41  	return &CrdLoader{
    42  		kubeClient: kubeClient,
    43  	}
    44  }
    45  
    46  // Find a matching CRD in the API server
    47  func (l *CrdLoader) GetCRDForKind(kind string) (*apiextensions.CustomResourceDefinition, error) {
    48  	return l.GetCRD("", "", kind)
    49  }
    50  
    51  // Find a matching CRD in the API server
    52  func (l *CrdLoader) GetCRDForGVK(gvk schema.GroupVersionKind) (*apiextensions.CustomResourceDefinition, error) {
    53  	return l.GetCRD(gvk.Group, gvk.Version, gvk.Kind)
    54  }
    55  
    56  // Find a matching CRD in the API server, the group and version parameters are optional
    57  func (l *CrdLoader) GetCRD(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
    58  	if kind == "" {
    59  		return nil, fmt.Errorf("invalid argument: 'kind' must contain a value")
    60  	}
    61  	if group == "" || version == "" {
    62  		return l.getCRDViaList(group, version, kind)
    63  	}
    64  	return l.getCRDViaGet(group, version, kind)
    65  }
    66  
    67  func (l *CrdLoader) getCRDViaList(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
    68  	listOptions := client.ListOptions{
    69  		Raw: &metav1.ListOptions{},
    70  	}
    71  	crds := make([]apiextensions.CustomResourceDefinition, 0)
    72  	for ok := true; ok; ok = listOptions.Raw.Continue != "" {
    73  		var list apiextensions.CustomResourceDefinitionList
    74  		if err := l.kubeClient.List(context.TODO(), &list, &listOptions); err != nil {
    75  			return nil, fmt.Errorf("error listing CRDs for GVK %v: %v", formatGVK(group, version, kind), err)
    76  		}
    77  		crds = append(crds, list.Items...)
    78  		listOptions.Raw.Continue = list.Continue
    79  	}
    80  	return getMatchingCRD(group, version, kind, crds)
    81  }
    82  
    83  func (l *CrdLoader) getCRDViaGet(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
    84  	lowercasePluralKind := strings.ToLower(text.Pluralize(kind))
    85  	var crd apiextensions.CustomResourceDefinition
    86  	nn := types.NamespacedName{Name: fmt.Sprintf("%v.%v", lowercasePluralKind, group)}
    87  	if err := l.kubeClient.Get(context.TODO(), nn, &crd); err != nil {
    88  		return nil, fmt.Errorf("error getting CRD for GVK %v: %v", formatGVK(group, version, kind), err)
    89  	}
    90  	return &crd, nil
    91  }
    92  
    93  // Find a matching CRD from disk
    94  func GetCRDForKind(kind string) (*apiextensions.CustomResourceDefinition, error) {
    95  	return GetCRD("", "", kind)
    96  }
    97  
    98  // Find a matching CRD from disk
    99  func GetCRDForGVK(gvk schema.GroupVersionKind) (*apiextensions.CustomResourceDefinition, error) {
   100  	return GetCRD(gvk.Group, gvk.Version, gvk.Kind)
   101  }
   102  
   103  // Find a matching CRD from disk, the group and version parameters are optional
   104  func GetCRD(group, version, kind string) (*apiextensions.CustomResourceDefinition, error) {
   105  	crds, err := LoadCRDs()
   106  	if err != nil {
   107  		return nil, fmt.Errorf("error loading CRDs: %v", err)
   108  	}
   109  	return getMatchingCRD(group, version, kind, crds)
   110  }
   111  
   112  func getMatchingCRD(group, version, kind string, crds []apiextensions.CustomResourceDefinition) (*apiextensions.CustomResourceDefinition, error) {
   113  	var match *apiextensions.CustomResourceDefinition
   114  	for _, crd := range crds {
   115  		if isMatch(group, version, kind, crd) {
   116  			if match == nil {
   117  				crd := crd
   118  				match = &crd
   119  			} else {
   120  				return nil, fmt.Errorf("ambiguous result: multiple CRDs match GVK parameter of %v", formatGVK(group, version, kind))
   121  			}
   122  		}
   123  	}
   124  	if match == nil {
   125  		return nil, fmt.Errorf("no CRD matches GVK parameter of %v", formatGVK(group, version, kind))
   126  	}
   127  	return match, nil
   128  }
   129  
   130  func isMatch(group, version, kind string, crd apiextensions.CustomResourceDefinition) bool {
   131  	if crd.Spec.Names.Kind != kind {
   132  		return false
   133  	}
   134  	if group != "" {
   135  		if crd.Spec.Group != group {
   136  			return false
   137  		}
   138  	}
   139  	if version != "" {
   140  		if k8s.GetVersionFromCRD(&crd) != version {
   141  			return false
   142  		}
   143  	}
   144  	return true
   145  }
   146  
   147  func formatGVK(group, version, kind string) string {
   148  	if group == "" {
   149  		group = "nil"
   150  	}
   151  	if version == "" {
   152  		version = "nil"
   153  	}
   154  	return fmt.Sprintf("{%v, %v, %v}", group, version, kind)
   155  }
   156  
   157  func LoadCRDs() ([]apiextensions.CustomResourceDefinition, error) {
   158  	crdsRoot := repo.GetCRDsPath()
   159  	files, err := ioutil.ReadDir(crdsRoot)
   160  	if err != nil {
   161  		return nil, fmt.Errorf("error listing directory '%v': %v", crdsRoot, err)
   162  	}
   163  	results := make([]apiextensions.CustomResourceDefinition, 0)
   164  	for _, crdFile := range files {
   165  		crd, err := FileToCRD(path.Join(crdsRoot, crdFile.Name()))
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		results = append(results, *crd)
   170  	}
   171  	return results, nil
   172  }
   173  
   174  func FileToCRD(fileName string) (*apiextensions.CustomResourceDefinition, error) {
   175  	bytes, err := ioutil.ReadFile(fileName)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("error reading file '%v': %v", fileName, err)
   178  	}
   179  	var crd apiextensions.CustomResourceDefinition
   180  	err = yaml.Unmarshal(bytes, &crd)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("error unmarshalling '%v' to CRD: %v", fileName, err)
   183  	}
   184  	return &crd, nil
   185  }
   186  

View as plain text