github.com/cilium/controller-tools@v0.3.1-0.20230329170030-f2b7ff866fde/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. 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 // NeedCRDFor requests the full CRD for the given group-kind. It requires 41 // that the packages containing the Go structs for that CRD have already 42 // been loaded with NeedPackage. 43 func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) { 44 p.init() 45 46 if _, exists := p.CustomResourceDefinitions[groupKind]; exists { 47 return 48 } 49 50 var packages []*loader.Package 51 for pkg, gv := range p.GroupVersions { 52 if gv.Group != groupKind.Group { 53 continue 54 } 55 packages = append(packages, pkg) 56 } 57 58 defaultPlural := flect.Pluralize(strings.ToLower(groupKind.Kind)) 59 crd := apiext.CustomResourceDefinition{ 60 TypeMeta: metav1.TypeMeta{ 61 APIVersion: apiext.SchemeGroupVersion.String(), 62 Kind: "CustomResourceDefinition", 63 }, 64 ObjectMeta: metav1.ObjectMeta{ 65 Name: defaultPlural + "." + groupKind.Group, 66 }, 67 Spec: apiext.CustomResourceDefinitionSpec{ 68 Group: groupKind.Group, 69 Names: apiext.CustomResourceDefinitionNames{ 70 Kind: groupKind.Kind, 71 ListKind: groupKind.Kind + "List", 72 Plural: defaultPlural, 73 Singular: strings.ToLower(groupKind.Kind), 74 }, 75 Scope: apiext.NamespaceScoped, 76 }, 77 } 78 79 for _, pkg := range packages { 80 typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind} 81 typeInfo := p.Types[typeIdent] 82 if typeInfo == nil { 83 continue 84 } 85 p.NeedFlattenedSchemaFor(typeIdent) 86 fullSchema := p.FlattenedSchemata[typeIdent] 87 fullSchema = *fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc) 88 if maxDescLen != nil { 89 TruncateDescription(&fullSchema, *maxDescLen) 90 } 91 ver := apiext.CustomResourceDefinitionVersion{ 92 Name: p.GroupVersions[pkg].Version, 93 Served: true, 94 Schema: &apiext.CustomResourceValidation{ 95 OpenAPIV3Schema: &fullSchema, // fine to take a reference since we deepcopy above 96 }, 97 } 98 crd.Spec.Versions = append(crd.Spec.Versions, ver) 99 } 100 101 // markers are applied *after* initial generation of objects 102 for _, pkg := range packages { 103 typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind} 104 typeInfo := p.Types[typeIdent] 105 if typeInfo == nil { 106 continue 107 } 108 ver := p.GroupVersions[pkg].Version 109 110 for _, markerVals := range typeInfo.Markers { 111 for _, val := range markerVals { 112 crdMarker, isCrdMarker := val.(SpecMarker) 113 if !isCrdMarker { 114 continue 115 } 116 if err := crdMarker.ApplyToCRD(&crd.Spec, ver); err != nil { 117 pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec)) 118 } 119 } 120 } 121 } 122 123 // fix the name if the plural was changed (this is the form the name *has* to take, so no harm in changing it). 124 crd.Name = crd.Spec.Names.Plural + "." + groupKind.Group 125 126 // nothing to actually write 127 if len(crd.Spec.Versions) == 0 { 128 return 129 } 130 131 // it is necessary to make sure the order of CRD versions in crd.Spec.Versions is stable and explicitly set crd.Spec.Version. 132 // Otherwise, crd.Spec.Version may point to different CRD versions across different runs. 133 sort.Slice(crd.Spec.Versions, func(i, j int) bool { return crd.Spec.Versions[i].Name < crd.Spec.Versions[j].Name }) 134 135 // make sure we have *a* storage version 136 // (default it if we only have one, otherwise, bail) 137 if len(crd.Spec.Versions) == 1 { 138 crd.Spec.Versions[0].Storage = true 139 } 140 141 hasStorage := false 142 for _, ver := range crd.Spec.Versions { 143 if ver.Storage { 144 hasStorage = true 145 break 146 } 147 } 148 if !hasStorage { 149 // just add the error to the first relevant package for this CRD, 150 // since there's no specific error location 151 packages[0].AddError(fmt.Errorf("CRD for %s has no storage version", groupKind)) 152 } 153 154 served := false 155 for _, ver := range crd.Spec.Versions { 156 if ver.Served { 157 served = true 158 break 159 } 160 } 161 if !served { 162 // just add the error to the first relevant package for this CRD, 163 // since there's no specific error location 164 packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions)) 165 } 166 167 // NB(directxman12): CRD's status doesn't have omitempty markers, which means things 168 // get serialized as null, which causes the validator to freak out. Manually set 169 // these to empty till we get a better solution. 170 crd.Status.Conditions = []apiext.CustomResourceDefinitionCondition{} 171 crd.Status.StoredVersions = []string{} 172 173 p.CustomResourceDefinitions[groupKind] = crd 174 }