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