istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/tools/featuresgen/cmd/root.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 cmd 16 17 import ( 18 "fmt" 19 "os" 20 "reflect" 21 "regexp" 22 "sort" 23 "strings" 24 25 "github.com/spf13/cobra" 26 "gopkg.in/yaml.v2" 27 ) 28 29 const ( 30 Copyright = `// Copyright Istio Authors 31 // 32 // Licensed under the Apache License, Version 2.0 (the "License"); 33 // you may not use this file except in compliance with the License. 34 // You may obtain a copy of the License at 35 // 36 // http://www.apache.org/licenses/LICENSE-2.0 37 // 38 // Unless required by applicable law or agreed to in writing, software 39 // distributed under the License is distributed on an "AS IS" BASIS, 40 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 // See the License for the specific language governing permissions and 42 // limitations under the License. 43 44 ` 45 46 GeneratedWarning = ` 47 //WARNING: THIS IS AN AUTO-GENERATED FILE, DO NOT EDIT. 48 ` 49 50 TypeDefFeature = ` 51 type Feature string 52 ` 53 54 Package = ` 55 package features 56 ` 57 58 ConstOpen = ` 59 const ( 60 ` 61 62 ConstClose = ` 63 ) 64 ` 65 ) 66 67 var ( 68 input string 69 output string 70 71 rootCmd = &cobra.Command{ 72 Use: "featuresgen [OPTIONS]", 73 Short: "FeaturesGen generates valid test labels from a yaml file", 74 Run: func(cmd *cobra.Command, args []string) { 75 createLabelsFile() 76 }, 77 } 78 79 alphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9-]`) 80 dotsRegex = regexp.MustCompile(`[\.]`) 81 replaceDashRegex = regexp.MustCompile(`-(.)`) 82 ) 83 84 func Execute() { 85 if err := rootCmd.Execute(); err != nil { 86 fmt.Println("Error running featuresgen:") 87 panic(err) 88 } 89 } 90 91 func init() { 92 rootCmd.Flags().StringVarP(&input, "inputFile", "i", "features.yaml", "the YAML file to use as input") 93 rootCmd.Flags().StringVarP(&output, "outputFile", "o", "features.gen.go", "output Go file with labels as string consts") 94 } 95 96 // Parses a map in the yaml file 97 func readMap(m map[any]any, path []string) []string { 98 var labels []string 99 for k, v := range m { 100 // If we see "values," then the element is a root and we shouldn't put it in our label name 101 if k == "values" { 102 labels = append(labels, readVal(v, path)...) 103 } else { 104 if len(path) > 0 || k.(string) != "features" { 105 path = append(path, k.(string)) 106 } 107 labels = append(labels, readVal(v, path)...) 108 if len(path) > 0 { 109 path = path[:len(path)-1] 110 } 111 } 112 } 113 return labels 114 } 115 116 // Parses a slice in the yaml file 117 func readSlice(slc []any, path []string) []string { 118 labels := make([]string, 0) 119 for _, v := range slc { 120 labels = append(labels, readVal(v, path)...) 121 } 122 return labels 123 } 124 125 // Determines the type of a node in the yaml file and parses it accordingly 126 func readVal(v any, path []string) []string { 127 typ := reflect.TypeOf(v).Kind() 128 if typ == reflect.Int || typ == reflect.String { 129 path = append(path, v.(string)) 130 return []string{createConstantString(path)} 131 } else if typ == reflect.Slice { 132 return readSlice(v.([]any), path) 133 } else if typ == reflect.Map { 134 return readMap(v.(map[any]any), path) 135 } 136 panic("Found invalid type in YAML file") 137 } 138 139 func removeDashAndTitle(s string) string { 140 // nolint: staticcheck 141 return strings.Title(s[1:]) 142 } 143 144 // Writes a label to the constants file 145 func createConstantString(path []string) string { 146 name := "" 147 value := "" 148 for i := 0; i < len(path); i++ { 149 namePart := alphanumericRegex.ReplaceAllString(path[i], "") 150 namePart = replaceDashRegex.ReplaceAllStringFunc(namePart, removeDashAndTitle) 151 // nolint: staticcheck 152 namePart = strings.Title(namePart) 153 name += namePart 154 name += "_" 155 156 value += dotsRegex.ReplaceAllString(path[i], "") 157 value += "." 158 } 159 name = strings.TrimSuffix(name, "_") 160 value = strings.TrimSuffix(value, ".") 161 return fmt.Sprintf("\t%s\tFeature = \"%s\"", name, value) 162 } 163 164 // Reads the yaml file and generates a string constant for each leaf node 165 func createLabelsFromYaml() string { 166 data, err := os.ReadFile(input) 167 if err != nil { 168 pwd, _ := os.Getwd() 169 fmt.Println("Error running featuresgen on file: ", pwd, "/", input) 170 panic(err) 171 } 172 m := make(map[any]any) 173 174 err = yaml.Unmarshal(data, &m) 175 if err != nil { 176 pwd, _ := os.Getwd() 177 fmt.Println("Error running featuresgen on file: ", pwd, "/", input) 178 panic(err) 179 } 180 181 constants := readVal(m, make([]string, 0)) 182 // The parsing of the yaml file doesn't seem to happen in a consistent order. To avoid a different file every time 'make gen' is run, we sort the list. 183 sort.Strings(constants) 184 return strings.Join(constants, "\n") 185 } 186 187 func check(err error) { 188 if err != nil { 189 panic(err) 190 } 191 } 192 193 // Main function that writes the new generated labels file 194 func createLabelsFile() { 195 f, err := os.Create("./" + output) 196 if err != nil { 197 fmt.Println("Error running featuresgen:") 198 panic(err) 199 } 200 201 defer f.Close() 202 203 _, err = f.WriteString(Copyright) 204 check(err) 205 _, err = f.WriteString(GeneratedWarning) 206 check(err) 207 _, err = f.WriteString(Package) 208 check(err) 209 _, err = f.WriteString(TypeDefFeature) 210 check(err) 211 _, err = f.WriteString(ConstOpen) 212 check(err) 213 _, err = f.WriteString(createLabelsFromYaml()) 214 check(err) 215 _, err = f.WriteString(ConstClose) 216 check(err) 217 err = f.Sync() 218 check(err) 219 }