genall.go 8.8 KB

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