github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/pkg/crd/spec.go (about)

     1  /*
     2  Copyright 2019 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  package crd
    17  
    18  import (
    19  	"fmt"
    20  	"sort"
    21  	"strings"
    22  
    23  	"github.com/gobuffalo/flect"
    24  
    25  	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  
    29  	"sigs.k8s.io/controller-tools/pkg/loader"
    30  )
    31  
    32  // SpecMarker is a marker that knows how to apply itself to a particular
    33  // version in a CRD Spec.
    34  type SpecMarker interface {
    35  	// ApplyToCRD applies this marker to the given CRD, in the given version
    36  	// within that CRD.  It's called after everything else in the CRD is populated.
    37  	ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error
    38  }
    39  
    40  // Marker is a marker that knows how to apply itself to a particular
    41  // version in a CRD.
    42  type Marker interface {
    43  	// ApplyToCRD applies this marker to the given CRD, in the given version
    44  	// within that CRD.  It's called after everything else in the CRD is populated.
    45  	ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error
    46  }
    47  
    48  // NeedCRDFor requests the full CRD for the given group-kind.  It requires
    49  // that the packages containing the Go structs for that CRD have already
    50  // been loaded with NeedPackage.
    51  func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
    52  	p.init()
    53  
    54  	if _, exists := p.CustomResourceDefinitions[groupKind]; exists {
    55  		return
    56  	}
    57  
    58  	var packages []*loader.Package
    59  	for pkg, gv := range p.GroupVersions {
    60  		if gv.Group != groupKind.Group {
    61  			continue
    62  		}
    63  		packages = append(packages, pkg)
    64  	}
    65  
    66  	defaultPlural := strings.ToLower(flect.Pluralize(groupKind.Kind))
    67  	crd := apiext.CustomResourceDefinition{
    68  		TypeMeta: metav1.TypeMeta{
    69  			APIVersion: apiext.SchemeGroupVersion.String(),
    70  			Kind:       "CustomResourceDefinition",
    71  		},
    72  		ObjectMeta: metav1.ObjectMeta{
    73  			Name: defaultPlural + "." + groupKind.Group,
    74  		},
    75  		Spec: apiext.CustomResourceDefinitionSpec{
    76  			Group: groupKind.Group,
    77  			Names: apiext.CustomResourceDefinitionNames{
    78  				Kind:     groupKind.Kind,
    79  				ListKind: groupKind.Kind + "List",
    80  				Plural:   defaultPlural,
    81  				Singular: strings.ToLower(groupKind.Kind),
    82  			},
    83  			Scope: apiext.NamespaceScoped,
    84  		},
    85  	}
    86  
    87  	for _, pkg := range packages {
    88  		typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind}
    89  		typeInfo := p.Types[typeIdent]
    90  		if typeInfo == nil {
    91  			continue
    92  		}
    93  		p.NeedFlattenedSchemaFor(typeIdent)
    94  		fullSchema := p.FlattenedSchemata[typeIdent]
    95  		fullSchema = *fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc)
    96  		if maxDescLen != nil {
    97  			TruncateDescription(&fullSchema, *maxDescLen)
    98  		}
    99  		ver := apiext.CustomResourceDefinitionVersion{
   100  			Name:   p.GroupVersions[pkg].Version,
   101  			Served: true,
   102  			Schema: &apiext.CustomResourceValidation{
   103  				OpenAPIV3Schema: &fullSchema, // fine to take a reference since we deepcopy above
   104  			},
   105  		}
   106  		crd.Spec.Versions = append(crd.Spec.Versions, ver)
   107  	}
   108  
   109  	// markers are applied *after* initial generation of objects
   110  	for _, pkg := range packages {
   111  		typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind}
   112  		typeInfo := p.Types[typeIdent]
   113  		if typeInfo == nil {
   114  			continue
   115  		}
   116  		ver := p.GroupVersions[pkg].Version
   117  
   118  		for _, markerVals := range typeInfo.Markers {
   119  			for _, val := range markerVals {
   120  				if specMarker, isSpecMarker := val.(SpecMarker); isSpecMarker {
   121  					if err := specMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
   122  						pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
   123  					}
   124  				} else if crdMarker, isCRDMarker := val.(Marker); isCRDMarker {
   125  					if err := crdMarker.ApplyToCRD(&crd, ver); err != nil {
   126  						pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
   127  					}
   128  				}
   129  			}
   130  		}
   131  	}
   132  
   133  	// fix the name if the plural was changed (this is the form the name *has* to take, so no harm in changing it).
   134  	crd.Name = crd.Spec.Names.Plural + "." + groupKind.Group
   135  
   136  	// nothing to actually write
   137  	if len(crd.Spec.Versions) == 0 {
   138  		return
   139  	}
   140  
   141  	// it is necessary to make sure the order of CRD versions in crd.Spec.Versions is stable and explicitly set crd.Spec.Version.
   142  	// Otherwise, crd.Spec.Version may point to different CRD versions across different runs.
   143  	sort.Slice(crd.Spec.Versions, func(i, j int) bool { return crd.Spec.Versions[i].Name < crd.Spec.Versions[j].Name })
   144  
   145  	// make sure we have *a* storage version
   146  	// (default it if we only have one, otherwise, bail)
   147  	if len(crd.Spec.Versions) == 1 {
   148  		crd.Spec.Versions[0].Storage = true
   149  	}
   150  
   151  	hasStorage := false
   152  	for _, ver := range crd.Spec.Versions {
   153  		if ver.Storage {
   154  			hasStorage = true
   155  			break
   156  		}
   157  	}
   158  	if !hasStorage {
   159  		// just add the error to the first relevant package for this CRD,
   160  		// since there's no specific error location
   161  		packages[0].AddError(fmt.Errorf("CRD for %s has no storage version", groupKind))
   162  	}
   163  
   164  	served := false
   165  	for _, ver := range crd.Spec.Versions {
   166  		if ver.Served {
   167  			served = true
   168  			break
   169  		}
   170  	}
   171  	if !served {
   172  		// just add the error to the first relevant package for this CRD,
   173  		// since there's no specific error location
   174  		packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions))
   175  	}
   176  
   177  	p.CustomResourceDefinitions[groupKind] = crd
   178  }