github.com/christarazi/controller-tools@v0.3.1-0.20210907042920-aa94049173f8/pkg/crd/markers/crd.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 17 package markers 18 19 import ( 20 "fmt" 21 22 apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 23 24 "sigs.k8s.io/controller-tools/pkg/markers" 25 ) 26 27 // CRDMarkers lists all markers that directly modify the CRD (not validation 28 // schemas). 29 var CRDMarkers = []*definitionWithHelp{ 30 // TODO(directxman12): more detailed help 31 must(markers.MakeDefinition("kubebuilder:subresource:status", markers.DescribesType, SubresourceStatus{})). 32 WithHelp(SubresourceStatus{}.Help()), 33 34 must(markers.MakeDefinition("kubebuilder:subresource:scale", markers.DescribesType, SubresourceScale{})). 35 WithHelp(SubresourceScale{}.Help()), 36 37 must(markers.MakeDefinition("kubebuilder:printcolumn", markers.DescribesType, PrintColumn{})). 38 WithHelp(PrintColumn{}.Help()), 39 40 must(markers.MakeDefinition("kubebuilder:resource", markers.DescribesType, Resource{})). 41 WithHelp(Resource{}.Help()), 42 43 must(markers.MakeDefinition("kubebuilder:storageversion", markers.DescribesType, StorageVersion{})). 44 WithHelp(StorageVersion{}.Help()), 45 46 must(markers.MakeDefinition("kubebuilder:skipversion", markers.DescribesType, SkipVersion{})). 47 WithHelp(SkipVersion{}.Help()), 48 49 must(markers.MakeDefinition("kubebuilder:unservedversion", markers.DescribesType, UnservedVersion{})). 50 WithHelp(UnservedVersion{}.Help()), 51 52 must(markers.MakeDefinition("kubebuilder:xpreserveunknownfields", markers.DescribesType, XPreserveUnknownFields{})). 53 WithHelp(XPreserveUnknownFields{}.Help()), 54 55 must(markers.MakeDefinition("kubebuilder:topleveldesc", markers.DescribesType, TopLevelDesc{})). 56 WithHelp(TopLevelDesc{}.Help()), 57 } 58 59 // TODO: categories and singular used to be annotations types 60 // TODO: doc 61 62 func init() { 63 AllDefinitions = append(AllDefinitions, CRDMarkers...) 64 } 65 66 // +controllertools:marker:generateHelp:category=CRD 67 68 // SubresourceStatus enables the "/status" subresource on a CRD. 69 type SubresourceStatus struct{} 70 71 func (s SubresourceStatus) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 72 var subresources *apiext.CustomResourceSubresources 73 for i := range crd.Versions { 74 ver := &crd.Versions[i] 75 if ver.Name != version { 76 continue 77 } 78 if ver.Subresources == nil { 79 ver.Subresources = &apiext.CustomResourceSubresources{} 80 } 81 subresources = ver.Subresources 82 break 83 } 84 if subresources == nil { 85 return fmt.Errorf("status subresource applied to version %q not in CRD", version) 86 } 87 subresources.Status = &apiext.CustomResourceSubresourceStatus{} 88 return nil 89 } 90 91 // +controllertools:marker:generateHelp:category=CRD 92 93 // SubresourceScale enables the "/scale" subresource on a CRD. 94 type SubresourceScale struct { 95 // marker names are leftover legacy cruft 96 97 // SpecPath specifies the jsonpath to the replicas field for the scale's spec. 98 SpecPath string `marker:"specpath"` 99 100 // StatusPath specifies the jsonpath to the replicas field for the scale's status. 101 StatusPath string `marker:"statuspath"` 102 103 // SelectorPath specifies the jsonpath to the pod label selector field for the scale's status. 104 // 105 // The selector field must be the *string* form (serialized form) of a selector. 106 // Setting a pod label selector is necessary for your type to work with the HorizontalPodAutoscaler. 107 SelectorPath *string `marker:"selectorpath"` 108 } 109 110 func (s SubresourceScale) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 111 var subresources *apiext.CustomResourceSubresources 112 for i := range crd.Versions { 113 ver := &crd.Versions[i] 114 if ver.Name != version { 115 continue 116 } 117 if ver.Subresources == nil { 118 ver.Subresources = &apiext.CustomResourceSubresources{} 119 } 120 subresources = ver.Subresources 121 break 122 } 123 if subresources == nil { 124 return fmt.Errorf("scale subresource applied to version %q not in CRD", version) 125 } 126 subresources.Scale = &apiext.CustomResourceSubresourceScale{ 127 SpecReplicasPath: s.SpecPath, 128 StatusReplicasPath: s.StatusPath, 129 LabelSelectorPath: s.SelectorPath, 130 } 131 return nil 132 } 133 134 // +controllertools:marker:generateHelp:category=CRD 135 136 // StorageVersion marks this version as the "storage version" for the CRD for conversion. 137 // 138 // When conversion is enabled for a CRD (i.e. it's not a trivial-versions/single-version CRD), 139 // one version is set as the "storage version" to be stored in etcd. Attempting to store any 140 // other version will result in conversion to the storage version via a conversion webhook. 141 type StorageVersion struct{} 142 143 func (s StorageVersion) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 144 if version == "" { 145 // single-version, do nothing 146 return nil 147 } 148 // multi-version 149 for i := range crd.Versions { 150 ver := &crd.Versions[i] 151 if ver.Name != version { 152 continue 153 } 154 ver.Storage = true 155 break 156 } 157 return nil 158 } 159 160 // +controllertools:marker:generateHelp:category=CRD 161 162 // SkipVersion removes the particular version of the CRD from the CRDs spec. 163 // 164 // This is useful if you need to skip generating and listing version entries 165 // for 'internal' resource versions, which typically exist if using the 166 // Kubernetes upstream conversion-gen tool. 167 type SkipVersion struct{} 168 169 func (s SkipVersion) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 170 if version == "" { 171 // single-version, this is an invalid state 172 return fmt.Errorf("cannot skip a version if there is only a single version") 173 } 174 var versions []apiext.CustomResourceDefinitionVersion 175 // multi-version 176 for i := range crd.Versions { 177 ver := crd.Versions[i] 178 if ver.Name == version { 179 // skip the skipped version 180 continue 181 } 182 versions = append(versions, ver) 183 } 184 crd.Versions = versions 185 return nil 186 } 187 188 // +controllertools:marker:generateHelp:category=CRD 189 190 // PrintColumn adds a column to "kubectl get" output for this CRD. 191 type PrintColumn struct { 192 // Name specifies the name of the column. 193 Name string 194 195 // Type indicates the type of the column. 196 // 197 // It may be any OpenAPI data type listed at 198 // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types. 199 Type string 200 201 // JSONPath specifies the jsonpath expression used to extract the value of the column. 202 JSONPath string `marker:"JSONPath"` // legacy cruft 203 204 // Description specifies the help/description for this column. 205 Description string `marker:",optional"` 206 207 // Format specifies the format of the column. 208 // 209 // It may be any OpenAPI data format corresponding to the type, listed at 210 // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types. 211 Format string `marker:",optional"` 212 213 // Priority indicates how important it is that this column be displayed. 214 // 215 // Lower priority (*higher* numbered) columns will be hidden if the terminal 216 // width is too small. 217 Priority int32 `marker:",optional"` 218 } 219 220 func (s PrintColumn) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 221 var columns *[]apiext.CustomResourceColumnDefinition 222 for i := range crd.Versions { 223 ver := &crd.Versions[i] 224 if ver.Name != version { 225 continue 226 } 227 if ver.Subresources == nil { 228 ver.Subresources = &apiext.CustomResourceSubresources{} 229 } 230 columns = &ver.AdditionalPrinterColumns 231 break 232 } 233 if columns == nil { 234 return fmt.Errorf("printer columns applied to version %q not in CRD", version) 235 } 236 237 *columns = append(*columns, apiext.CustomResourceColumnDefinition{ 238 Name: s.Name, 239 Type: s.Type, 240 JSONPath: s.JSONPath, 241 Description: s.Description, 242 Format: s.Format, 243 Priority: s.Priority, 244 }) 245 246 return nil 247 } 248 249 // +controllertools:marker:generateHelp:category=CRD 250 251 // Resource configures naming and scope for a CRD. 252 type Resource struct { 253 // Path specifies the plural "resource" for this CRD. 254 // 255 // It generally corresponds to a plural, lower-cased version of the Kind. 256 // See https://book.kubebuilder.io/cronjob-tutorial/gvks.html. 257 Path string `marker:",optional"` 258 259 // ShortName specifies aliases for this CRD. 260 // 261 // Short names are often used when people have work with your resource 262 // over and over again. For instance, "rs" for "replicaset" or 263 // "crd" for customresourcedefinition. 264 ShortName []string `marker:",optional"` 265 266 // Categories specifies which group aliases this resource is part of. 267 // 268 // Group aliases are used to work with groups of resources at once. 269 // The most common one is "all" which covers about a third of the base 270 // resources in Kubernetes, and is generally used for "user-facing" resources. 271 Categories []string `marker:",optional"` 272 273 // Singular overrides the singular form of your resource. 274 // 275 // The singular form is otherwise defaulted off the plural (path). 276 Singular string `marker:",optional"` 277 278 // Scope overrides the scope of the CRD (Cluster vs Namespaced). 279 // 280 // Scope defaults to "Namespaced". Cluster-scoped ("Cluster") resources 281 // don't exist in namespaces. 282 Scope string `marker:",optional"` 283 } 284 285 func (s Resource) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 286 if s.Path != "" { 287 crd.Names.Plural = s.Path 288 } 289 if s.Singular != "" { 290 crd.Names.Singular = s.Singular 291 } 292 crd.Names.ShortNames = s.ShortName 293 crd.Names.Categories = s.Categories 294 295 switch s.Scope { 296 case "": 297 crd.Scope = apiext.NamespaceScoped 298 default: 299 crd.Scope = apiext.ResourceScope(s.Scope) 300 } 301 302 return nil 303 } 304 305 // +controllertools:marker:generateHelp:category=CRD 306 307 // UnservedVersion does not serve this version. 308 // 309 // This is useful if you need to drop support for a version in favor of a newer version. 310 type UnservedVersion struct{} 311 312 func (s UnservedVersion) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 313 for i := range crd.Versions { 314 ver := &crd.Versions[i] 315 if ver.Name != version { 316 continue 317 } 318 ver.Served = false 319 break 320 } 321 return nil 322 } 323 324 // +controllertools:marker:generateHelp:category=CRD 325 326 // TopLevelDesc adds "description" to the top-level validation schema. 327 // 328 // This is useful for CRDs that want a top-level description field to describe 329 // the resource. Specifying this marker will add a description field at the 330 // top-level to the validation schema. 331 type TopLevelDesc struct{} 332 333 func (s TopLevelDesc) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { 334 for i := range crd.Versions { 335 ver := &crd.Versions[i] 336 if ver.Name != version { 337 continue 338 } 339 ver.Schema.OpenAPIV3Schema.Properties["description"] = apiext.JSONSchemaProps{ 340 Description: "Description is a human-readable description of the resource.", 341 Type: "string", 342 } 343 break 344 } 345 return nil 346 } 347 348 // NB(directxman12): singular was historically distinct, so we keep it here for backwards compat