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  }