istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/schema/codegen/collections.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package codegen 16 17 import ( 18 "bytes" 19 _ "embed" 20 "fmt" 21 "os" 22 "path/filepath" 23 "sort" 24 "strings" 25 26 "github.com/stoewer/go-strcase" 27 28 "istio.io/istio/pkg/config/schema/ast" 29 "istio.io/istio/pkg/test/env" 30 "istio.io/istio/pkg/util/sets" 31 ) 32 33 //go:embed templates/gvk.go.tmpl 34 var gvkTemplate string 35 36 //go:embed templates/gvr.go.tmpl 37 var gvrTemplate string 38 39 //go:embed templates/crdclient.go.tmpl 40 var crdclientTemplate string 41 42 //go:embed templates/types.go.tmpl 43 var typesTemplate string 44 45 //go:embed templates/clients.go.tmpl 46 var clientsTemplate string 47 48 //go:embed templates/kind.go.tmpl 49 var kindTemplate string 50 51 //go:embed templates/collections.go.tmpl 52 var collectionsTemplate string 53 54 type colEntry struct { 55 Resource *ast.Resource 56 57 // ClientImport represents the import alias for the client. Example: clientnetworkingv1alpha3. 58 ClientImport string 59 // ClientImport represents the import alias for the status. Example: clientnetworkingv1alpha3. 60 StatusImport string 61 // IstioAwareClientImport represents the import alias for the API, taking into account Istio storing its API (spec) 62 // separate from its client import 63 // Example: apiclientnetworkingv1alpha3. 64 IstioAwareClientImport string 65 // ClientGroupPath represents the group in the client. Example: NetworkingV1alpha3. 66 ClientGroupPath string 67 // ClientGetter returns the path to get the client from a kube.Client. Example: Istio. 68 ClientGetter string 69 // ClientTypePath returns the kind name. Basically upper cased "plural". Example: Gateways 70 ClientTypePath string 71 // SpecType returns the type of the Spec field. Example: HTTPRouteSpec. 72 SpecType string 73 StatusType string 74 } 75 76 type inputs struct { 77 Entries []colEntry 78 Packages []packageImport 79 } 80 81 func buildInputs() (inputs, error) { 82 b, err := os.ReadFile(filepath.Join(env.IstioSrc, "pkg/config/schema/metadata.yaml")) 83 if err != nil { 84 fmt.Printf("unable to read input file: %v", err) 85 return inputs{}, err 86 } 87 88 // Parse the file. 89 m, err := ast.Parse(string(b)) 90 if err != nil { 91 fmt.Printf("failed parsing input file: %v", err) 92 return inputs{}, err 93 } 94 entries := make([]colEntry, 0, len(m.Resources)) 95 for _, r := range m.Resources { 96 spl := strings.Split(r.Proto, ".") 97 tname := spl[len(spl)-1] 98 stat := strings.Split(r.StatusProto, ".") 99 statName := stat[len(stat)-1] 100 e := colEntry{ 101 Resource: r, 102 ClientImport: toImport(r.ProtoPackage), 103 StatusImport: toImport(r.StatusProtoPackage), 104 IstioAwareClientImport: toIstioAwareImport(r.ProtoPackage), 105 ClientGroupPath: toGroup(r.ProtoPackage), 106 ClientGetter: toGetter(r.ProtoPackage), 107 ClientTypePath: toTypePath(r), 108 SpecType: tname, 109 } 110 if r.StatusProtoPackage != "" { 111 e.StatusType = statName 112 } 113 entries = append(entries, e) 114 } 115 116 sort.Slice(entries, func(i, j int) bool { 117 return strings.Compare(entries[i].Resource.Identifier, entries[j].Resource.Identifier) < 0 118 }) 119 120 // Single instance and sort names 121 names := sets.New[string]() 122 123 for _, r := range m.Resources { 124 if r.ProtoPackage != "" { 125 names.Insert(r.ProtoPackage) 126 } 127 if r.StatusProtoPackage != "" { 128 names.Insert(r.StatusProtoPackage) 129 } 130 } 131 132 packages := make([]packageImport, 0, names.Len()) 133 for p := range names { 134 packages = append(packages, packageImport{p, toImport(p)}) 135 } 136 sort.Slice(packages, func(i, j int) bool { 137 return strings.Compare(packages[i].PackageName, packages[j].PackageName) < 0 138 }) 139 140 return inputs{ 141 Entries: entries, 142 Packages: packages, 143 }, nil 144 } 145 146 func toTypePath(r *ast.Resource) string { 147 k := r.Kind 148 g := r.Plural 149 res := strings.Builder{} 150 for i, c := range g { 151 if i >= len(k) { 152 res.WriteByte(byte(c)) 153 } else { 154 if k[i] == bytes.ToUpper([]byte{byte(c)})[0] { 155 res.WriteByte(k[i]) 156 } else { 157 res.WriteByte(byte(c)) 158 } 159 } 160 } 161 return res.String() 162 } 163 164 func toGetter(protoPackage string) string { 165 if strings.Contains(protoPackage, "istio.io") { 166 return "Istio" 167 } else if strings.Contains(protoPackage, "sigs.k8s.io/gateway-api") { 168 return "GatewayAPI" 169 } else if strings.Contains(protoPackage, "k8s.io/apiextensions-apiserver") { 170 return "Ext" 171 } 172 return "Kube" 173 } 174 175 func toGroup(protoPackage string) string { 176 p := strings.Split(protoPackage, "/") 177 e := len(p) - 1 178 if strings.Contains(protoPackage, "sigs.k8s.io/gateway-api") { 179 // Gateway has one level of nesting with custom name 180 return "Gateway" + strcase.UpperCamelCase(p[e]) 181 } 182 // rest have two levels of nesting 183 return strcase.UpperCamelCase(p[e-1]) + strcase.UpperCamelCase(p[e]) 184 } 185 186 type packageImport struct { 187 PackageName string 188 ImportName string 189 } 190 191 func toImport(p string) string { 192 return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(p, "/", ""), ".", ""), "-", "") 193 } 194 195 func toIstioAwareImport(p string) string { 196 imp := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(p, "/", ""), ".", ""), "-", "") 197 if strings.Contains(p, "istio.io") { 198 return "api" + imp 199 } 200 return imp 201 }