sigs.k8s.io/gateway-api@v1.0.0/pkg/generator/main.go (about) 1 /* 2 Copyright 2021 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 main 18 19 import ( 20 "fmt" 21 "log" 22 "os" 23 "regexp" 24 "strings" 25 26 apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 27 "sigs.k8s.io/controller-tools/pkg/crd" 28 "sigs.k8s.io/controller-tools/pkg/loader" 29 "sigs.k8s.io/controller-tools/pkg/markers" 30 "sigs.k8s.io/yaml" 31 ) 32 33 const ( 34 bundleVersionAnnotation = "gateway.networking.k8s.io/bundle-version" 35 channelAnnotation = "gateway.networking.k8s.io/channel" 36 37 // These values must be updated during the release process 38 bundleVersion = "v1.0.0" 39 approvalLink = "https://github.com/kubernetes-sigs/gateway-api/pull/2466" 40 ) 41 42 var standardKinds = map[string]bool{ 43 "GatewayClass": true, 44 "Gateway": true, 45 "HTTPRoute": true, 46 "ReferenceGrant": true, 47 } 48 49 // This generation code is largely copied from 50 // github.com/kubernetes-sigs/controller-tools/blob/ab52f76cc7d167925b2d5942f24bf22e30f49a02/pkg/crd/gen.go 51 func main() { 52 roots, err := loader.LoadRoots( 53 "k8s.io/apimachinery/pkg/runtime/schema", // Needed to parse generated register functions. 54 "sigs.k8s.io/gateway-api/apis/v1alpha2", 55 "sigs.k8s.io/gateway-api/apis/v1beta1", 56 "sigs.k8s.io/gateway-api/apis/v1", 57 ) 58 if err != nil { 59 log.Fatalf("failed to load package roots: %s", err) 60 } 61 62 generator := &crd.Generator{} 63 64 parser := &crd.Parser{ 65 Collector: &markers.Collector{Registry: &markers.Registry{}}, 66 Checker: &loader.TypeChecker{ 67 NodeFilters: []loader.NodeFilter{generator.CheckFilter()}, 68 }, 69 } 70 71 err = generator.RegisterMarkers(parser.Collector.Registry) 72 if err != nil { 73 log.Fatalf("failed to register markers: %s", err) 74 } 75 76 crd.AddKnownTypes(parser) 77 for _, r := range roots { 78 parser.NeedPackage(r) 79 } 80 81 metav1Pkg := crd.FindMetav1(roots) 82 if metav1Pkg == nil { 83 log.Fatalf("no objects in the roots, since nothing imported metav1") 84 } 85 86 kubeKinds := crd.FindKubeKinds(parser, metav1Pkg) 87 if len(kubeKinds) == 0 { 88 log.Fatalf("no objects in the roots") 89 } 90 91 channels := []string{"standard", "experimental"} 92 for _, channel := range channels { 93 for _, groupKind := range kubeKinds { 94 if channel == "standard" && !standardKinds[groupKind.Kind] { 95 continue 96 } 97 98 log.Printf("generating %s CRD for %v\n", channel, groupKind) 99 100 parser.NeedCRDFor(groupKind, nil) 101 crdRaw := parser.CustomResourceDefinitions[groupKind] 102 103 // Inline version of "addAttribution(&crdRaw)" ... 104 if crdRaw.ObjectMeta.Annotations == nil { 105 crdRaw.ObjectMeta.Annotations = map[string]string{} 106 } 107 crdRaw.ObjectMeta.Annotations[bundleVersionAnnotation] = bundleVersion 108 crdRaw.ObjectMeta.Annotations[channelAnnotation] = channel 109 crdRaw.ObjectMeta.Annotations[apiext.KubeAPIApprovedAnnotation] = approvalLink 110 111 // Prevent the top level metadata for the CRD to be generated regardless of the intention in the arguments 112 crd.FixTopLevelMetadata(crdRaw) 113 114 channelCrd := crdRaw.DeepCopy() 115 for _, version := range channelCrd.Spec.Versions { 116 version.Schema.OpenAPIV3Schema.Properties = gatewayTweaks(channel, version.Schema.OpenAPIV3Schema.Properties) 117 } 118 119 conv, err := crd.AsVersion(*channelCrd, apiext.SchemeGroupVersion) 120 if err != nil { 121 log.Fatalf("failed to convert CRD: %s", err) 122 } 123 124 out, err := yaml.Marshal(conv) 125 if err != nil { 126 log.Fatalf("failed to marshal CRD: %s", err) 127 } 128 129 fileName := fmt.Sprintf("config/crd/%s/%s_%s.yaml", channel, crdRaw.Spec.Group, crdRaw.Spec.Names.Plural) 130 err = os.WriteFile(fileName, out, 0o600) 131 if err != nil { 132 log.Fatalf("failed to write CRD: %s", err) 133 } 134 } 135 } 136 } 137 138 // Custom Gateway API Tweaks for tags prefixed with `<gateway:` that get past 139 // the limitations of Kubebuilder annotations. 140 func gatewayTweaks(channel string, props map[string]apiext.JSONSchemaProps) map[string]apiext.JSONSchemaProps { 141 for name := range props { 142 jsonProps, _ := props[name] 143 144 if strings.Contains(jsonProps.Description, "<gateway:validateIPAddress>") { 145 jsonProps.Items.Schema.OneOf = []apiext.JSONSchemaProps{{ 146 Properties: map[string]apiext.JSONSchemaProps{ 147 "type": { 148 Enum: []apiext.JSON{{Raw: []byte("\"IPAddress\"")}}, 149 }, 150 "value": { 151 AnyOf: []apiext.JSONSchemaProps{{ 152 Format: "ipv4", 153 }, { 154 Format: "ipv6", 155 }}, 156 }, 157 }, 158 }, { 159 Properties: map[string]apiext.JSONSchemaProps{ 160 "type": { 161 Not: &apiext.JSONSchemaProps{ 162 Enum: []apiext.JSON{{Raw: []byte("\"IPAddress\"")}}, 163 }, 164 }, 165 }, 166 }} 167 } 168 169 if channel == "standard" && strings.Contains(jsonProps.Description, "<gateway:experimental>") { 170 delete(props, name) 171 continue 172 } 173 174 // TODO(robscott): Figure out why crdgen switched this to "object" 175 if jsonProps.Format == "date-time" { 176 jsonProps.Type = "string" 177 } 178 179 validationPrefix := fmt.Sprintf("<gateway:%s:validation:", channel) 180 numExpressions := strings.Count(jsonProps.Description, validationPrefix) 181 numValid := 0 182 if numExpressions > 0 { 183 enumRe := regexp.MustCompile(validationPrefix + "Enum=([A-Za-z;]*)>") 184 enumMatches := enumRe.FindAllStringSubmatch(jsonProps.Description, 64) 185 for _, enumMatch := range enumMatches { 186 if len(enumMatch) != 2 { 187 log.Fatalf("Invalid %s Enum tag for %s", validationPrefix, name) 188 } 189 190 numValid++ 191 jsonProps.Enum = []apiext.JSON{} 192 for _, val := range strings.Split(enumMatch[1], ";") { 193 jsonProps.Enum = append(jsonProps.Enum, apiext.JSON{Raw: []byte("\"" + val + "\"")}) 194 } 195 } 196 197 celRe := regexp.MustCompile(validationPrefix + "XValidation:message=\"([^\"]*)\",rule=\"([^\"]*)\">") 198 celMatches := celRe.FindAllStringSubmatch(jsonProps.Description, 64) 199 for _, celMatch := range celMatches { 200 if len(celMatch) != 3 { 201 log.Fatalf("Invalid %s CEL tag for %s", validationPrefix, name) 202 } 203 204 numValid++ 205 jsonProps.XValidations = append(jsonProps.XValidations, apiext.ValidationRule{ 206 Message: celMatch[1], 207 Rule: celMatch[2], 208 }) 209 } 210 } 211 startTag := "<gateway:experimental:description>" 212 endTag := "</gateway:experimental:description>" 213 regexPattern := regexp.QuoteMeta(startTag) + `(?s:(.*?))` + regexp.QuoteMeta(endTag) 214 if channel == "standard" && strings.Contains(jsonProps.Description, "<gateway:experimental:description>") { 215 re := regexp.MustCompile(regexPattern) 216 match := re.FindStringSubmatch(jsonProps.Description) 217 if len(match) != 2 { 218 log.Fatalf("Invalid <gateway:experimental:description> tag for %s", name) 219 } 220 modifiedDescription := re.ReplaceAllString(jsonProps.Description, "") 221 jsonProps.Description = modifiedDescription 222 } else { 223 jsonProps.Description = strings.ReplaceAll(jsonProps.Description, startTag, "") 224 jsonProps.Description = strings.ReplaceAll(jsonProps.Description, endTag, "") 225 } 226 227 if numValid < numExpressions { 228 fmt.Printf("Description: %s\n", jsonProps.Description) 229 log.Fatalf("Found %d Gateway validation expressions, but only %d were valid", numExpressions, numValid) 230 } 231 232 gatewayRe := regexp.MustCompile(`<gateway:.*>`) 233 jsonProps.Description = gatewayRe.ReplaceAllLiteralString(jsonProps.Description, "") 234 235 if len(jsonProps.Properties) > 0 { 236 jsonProps.Properties = gatewayTweaks(channel, jsonProps.Properties) 237 } else if jsonProps.Items != nil && jsonProps.Items.Schema != nil { 238 jsonProps.Items.Schema.Properties = gatewayTweaks(channel, jsonProps.Items.Schema.Properties) 239 } 240 props[name] = jsonProps 241 } 242 return props 243 }