sigs.k8s.io/controller-tools@v0.15.1-0.20240515195456-85686cb69316/pkg/genall/genall.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 genall 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "os" 24 25 "golang.org/x/tools/go/packages" 26 rawyaml "gopkg.in/yaml.v2" 27 28 "sigs.k8s.io/controller-tools/pkg/loader" 29 "sigs.k8s.io/controller-tools/pkg/markers" 30 ) 31 32 // Generators are a list of Generators. 33 // NB(directxman12): this is a pointer so that we can uniquely identify each 34 // instance of a generator, even if it's not hashable. Different *instances* 35 // of a generator are treated differently. 36 type Generators []*Generator 37 38 // RegisterMarkers registers all markers defined by each of the Generators in 39 // this list into the given registry. 40 func (g Generators) RegisterMarkers(reg *markers.Registry) error { 41 for _, gen := range g { 42 if err := (*gen).RegisterMarkers(reg); err != nil { 43 return err 44 } 45 } 46 return nil 47 } 48 49 // CheckFilters returns the set of NodeFilters for all Generators that 50 // implement NeedsTypeChecking. 51 func (g Generators) CheckFilters() []loader.NodeFilter { 52 var filters []loader.NodeFilter 53 for _, gen := range g { 54 withFilter, needsChecking := (*gen).(NeedsTypeChecking) 55 if !needsChecking { 56 continue 57 } 58 filters = append(filters, withFilter.CheckFilter()) 59 } 60 return filters 61 } 62 63 // NeedsTypeChecking indicates that a particular generator needs & has opinions 64 // on typechecking. If this is not implemented, a generator will be given a 65 // context with a nil typechecker. 66 type NeedsTypeChecking interface { 67 // CheckFilter indicates the loader.NodeFilter (if any) that should be used 68 // to prune out unused types/packages when type-checking (nodes for which 69 // the filter returns true are considered "interesting"). This filter acts 70 // as a baseline -- all types the pass through this filter will be checked, 71 // but more than that may also be checked due to other generators' filters. 72 CheckFilter() loader.NodeFilter 73 } 74 75 // Generator knows how to register some set of markers, and then produce 76 // output artifacts based on loaded code containing those markers, 77 // sharing common loaded data. 78 type Generator interface { 79 // RegisterMarkers registers all markers needed by this Generator 80 // into the given registry. 81 RegisterMarkers(into *markers.Registry) error 82 // Generate generates artifacts produced by this marker. 83 // It's called *after* RegisterMarkers has been called. 84 Generate(*GenerationContext) error 85 } 86 87 // HasHelp is some Generator, OutputRule, etc with a help method. 88 type HasHelp interface { 89 // Help returns help for this generator. 90 Help() *markers.DefinitionHelp 91 } 92 93 // Runtime collects generators, loaded program data (Collector, root Packages), 94 // and I/O rules, running them together. 95 type Runtime struct { 96 // Generators are the Generators to be run by this Runtime. 97 Generators Generators 98 // GenerationContext is the base generation context that's copied 99 // to produce the context for each Generator. 100 GenerationContext 101 // OutputRules defines how to output artifacts for each Generator. 102 OutputRules OutputRules 103 // ErrorWriter defines where to write error messages. 104 ErrorWriter io.Writer 105 } 106 107 // GenerationContext defines the common information needed for each Generator 108 // to run. 109 type GenerationContext struct { 110 // Collector is the shared marker collector. 111 Collector *markers.Collector 112 // Roots are the base packages to be processed. 113 Roots []*loader.Package 114 // Checker is the shared partial type-checker. 115 Checker *loader.TypeChecker 116 // OutputRule describes how to output artifacts. 117 OutputRule 118 // InputRule describes how to load associated boilerplate artifacts. 119 // It should *not* be used to load source files. 120 InputRule 121 } 122 123 // WriteYAMLOptions implements the Options Pattern for WriteYAML. 124 type WriteYAMLOptions struct { 125 transform func(obj map[string]interface{}) error 126 } 127 128 // WithTransform applies a transformation to objects just before writing them. 129 func WithTransform(transform func(obj map[string]interface{}) error) *WriteYAMLOptions { 130 return &WriteYAMLOptions{ 131 transform: transform, 132 } 133 } 134 135 // TransformRemoveCreationTimestamp ensures we do not write the metadata.creationTimestamp field. 136 func TransformRemoveCreationTimestamp(obj map[string]interface{}) error { 137 metadata := obj["metadata"].(map[interface{}]interface{}) 138 delete(metadata, "creationTimestamp") 139 return nil 140 } 141 142 // WriteYAML writes the given objects out, serialized as YAML, using the 143 // context's OutputRule. Objects are written as separate documents, separated 144 // from each other by `---` (as per the YAML spec). 145 func (g GenerationContext) WriteYAML(itemPath, headerText string, objs []interface{}, options ...*WriteYAMLOptions) error { 146 out, err := g.Open(nil, itemPath) 147 if err != nil { 148 return err 149 } 150 defer out.Close() 151 152 _, err = out.Write([]byte(headerText)) 153 if err != nil { 154 return err 155 } 156 157 for _, obj := range objs { 158 yamlContent, err := yamlMarshal(obj, options...) 159 if err != nil { 160 return err 161 } 162 n, err := out.Write(append([]byte("---\n"), yamlContent...)) 163 if err != nil { 164 return err 165 } 166 if n < len(yamlContent) { 167 return io.ErrShortWrite 168 } 169 } 170 171 return nil 172 } 173 174 // yamlMarshal is based on sigs.k8s.io/yaml.Marshal, but allows for transforming the final data before writing. 175 func yamlMarshal(o interface{}, options ...*WriteYAMLOptions) ([]byte, error) { 176 j, err := json.Marshal(o) 177 if err != nil { 178 return nil, fmt.Errorf("error marshaling into JSON: %v", err) 179 } 180 181 return yamlJSONToYAMLWithFilter(j, options...) 182 } 183 184 // yamlJSONToYAMLWithFilter is based on sigs.k8s.io/yaml.JSONToYAML, but allows for transforming the final data before writing. 185 func yamlJSONToYAMLWithFilter(j []byte, options ...*WriteYAMLOptions) ([]byte, error) { 186 // Convert the JSON to an object. 187 var jsonObj map[string]interface{} 188 // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the 189 // Go JSON library doesn't try to pick the right number type (int, float, 190 // etc.) when unmarshalling to interface{}, it just picks float64 191 // universally. go-yaml does go through the effort of picking the right 192 // number type, so we can preserve number type throughout this process. 193 if err := rawyaml.Unmarshal(j, &jsonObj); err != nil { 194 return nil, err 195 } 196 197 for _, option := range options { 198 if option.transform != nil { 199 if err := option.transform(jsonObj); err != nil { 200 return nil, err 201 } 202 } 203 } 204 205 // Marshal this object into YAML. 206 return rawyaml.Marshal(jsonObj) 207 } 208 209 // ReadFile reads the given boilerplate artifact using the context's InputRule. 210 func (g GenerationContext) ReadFile(path string) ([]byte, error) { 211 file, err := g.OpenForRead(path) 212 if err != nil { 213 return nil, err 214 } 215 defer file.Close() 216 return io.ReadAll(file) 217 } 218 219 // ForRoots produces a Runtime to run the given generators against the 220 // given packages. It outputs to /dev/null by default. 221 func (g Generators) ForRoots(rootPaths ...string) (*Runtime, error) { 222 roots, err := loader.LoadRoots(rootPaths...) 223 if err != nil { 224 return nil, err 225 } 226 rt := &Runtime{ 227 Generators: g, 228 GenerationContext: GenerationContext{ 229 Collector: &markers.Collector{ 230 Registry: &markers.Registry{}, 231 }, 232 Roots: roots, 233 InputRule: InputFromFileSystem, 234 Checker: &loader.TypeChecker{ 235 NodeFilters: g.CheckFilters(), 236 }, 237 }, 238 OutputRules: OutputRules{Default: OutputToNothing}, 239 } 240 if err := rt.Generators.RegisterMarkers(rt.Collector.Registry); err != nil { 241 return nil, err 242 } 243 return rt, nil 244 } 245 246 // Run runs the Generators in this Runtime against its packages, printing 247 // errors (except type errors, which common result from using TypeChecker with 248 // filters), returning true if errors were found. 249 func (r *Runtime) Run() bool { 250 // TODO(directxman12): we could make this parallel, 251 // but we'd need to ensure all underlying machinery is threadsafe 252 253 if r.ErrorWriter == nil { 254 r.ErrorWriter = os.Stderr 255 } 256 if len(r.Generators) == 0 { 257 fmt.Fprintln(r.ErrorWriter, "no generators to run") 258 return true 259 } 260 261 hadErrs := false 262 for _, gen := range r.Generators { 263 ctx := r.GenerationContext // make a shallow copy 264 ctx.OutputRule = r.OutputRules.ForGenerator(gen) 265 266 // don't pass a typechecker to generators that don't provide a filter 267 // to avoid accidents 268 if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking { 269 ctx.Checker = nil 270 } 271 272 if err := (*gen).Generate(&ctx); err != nil { 273 fmt.Fprintln(r.ErrorWriter, err) 274 hadErrs = true 275 } 276 } 277 278 // skip TypeErrors -- they're probably just from partial typechecking in crd-gen 279 return loader.PrintErrors(r.Roots, packages.TypeError) || hadErrs 280 }