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