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  }