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