sigs.k8s.io/controller-tools@v0.15.1-0.20240515195456-85686cb69316/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 }