github.com/matthchr/controller-tools@v0.3.1-0.20200602225425-d33ced351ff8/cmd/controller-gen/main.go (about)

     1  /*
     2  Copyright 2018 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  package main
    17  
    18  import (
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"strings"
    24  
    25  	"github.com/spf13/cobra"
    26  
    27  	"github.com/matthchr/controller-tools/pkg/crd"
    28  	"github.com/matthchr/controller-tools/pkg/deepcopy"
    29  	"github.com/matthchr/controller-tools/pkg/genall"
    30  	"github.com/matthchr/controller-tools/pkg/genall/help"
    31  	prettyhelp "github.com/matthchr/controller-tools/pkg/genall/help/pretty"
    32  	"github.com/matthchr/controller-tools/pkg/markers"
    33  	"github.com/matthchr/controller-tools/pkg/rbac"
    34  	"github.com/matthchr/controller-tools/pkg/schemapatcher"
    35  	"github.com/matthchr/controller-tools/pkg/version"
    36  	"github.com/matthchr/controller-tools/pkg/webhook"
    37  )
    38  
    39  //go:generate go run ../helpgen/main.go paths=../../pkg/... generate:headerFile=../../boilerplate.go.txt,year=2019
    40  
    41  // Options are specified to controller-gen by turning generators and output rules into
    42  // markers, and then parsing them using the standard registry logic (without the "+").
    43  // Each marker and output rule should thus be usable as a marker target.
    44  
    45  var (
    46  	// allGenerators maintains the list of all known generators, giving
    47  	// them names for use on the command line.
    48  	// each turns into a command line option,
    49  	// and has options for output forms.
    50  	allGenerators = map[string]genall.Generator{
    51  		"crd":         crd.Generator{},
    52  		"rbac":        rbac.Generator{},
    53  		"object":      deepcopy.Generator{},
    54  		"webhook":     webhook.Generator{},
    55  		"schemapatch": schemapatcher.Generator{},
    56  	}
    57  
    58  	// allOutputRules defines the list of all known output rules, giving
    59  	// them names for use on the command line.
    60  	// Each output rule turns into two command line options:
    61  	// - output:<generator>:<form> (per-generator output)
    62  	// - output:<form> (default output)
    63  	allOutputRules = map[string]genall.OutputRule{
    64  		"dir":       genall.OutputToDirectory(""),
    65  		"none":      genall.OutputToNothing,
    66  		"stdout":    genall.OutputToStdout,
    67  		"artifacts": genall.OutputArtifacts{},
    68  	}
    69  
    70  	// optionsRegistry contains all the marker definitions used to process command line options
    71  	optionsRegistry = &markers.Registry{}
    72  )
    73  
    74  func init() {
    75  	for genName, gen := range allGenerators {
    76  		// make the generator options marker itself
    77  		defn := markers.Must(markers.MakeDefinition(genName, markers.DescribesPackage, gen))
    78  		if err := optionsRegistry.Register(defn); err != nil {
    79  			panic(err)
    80  		}
    81  		if helpGiver, hasHelp := gen.(genall.HasHelp); hasHelp {
    82  			if help := helpGiver.Help(); help != nil {
    83  				optionsRegistry.AddHelp(defn, help)
    84  			}
    85  		}
    86  
    87  		// make per-generation output rule markers
    88  		for ruleName, rule := range allOutputRules {
    89  			ruleMarker := markers.Must(markers.MakeDefinition(fmt.Sprintf("output:%s:%s", genName, ruleName), markers.DescribesPackage, rule))
    90  			if err := optionsRegistry.Register(ruleMarker); err != nil {
    91  				panic(err)
    92  			}
    93  			if helpGiver, hasHelp := rule.(genall.HasHelp); hasHelp {
    94  				if help := helpGiver.Help(); help != nil {
    95  					optionsRegistry.AddHelp(ruleMarker, help)
    96  				}
    97  			}
    98  		}
    99  	}
   100  
   101  	// make "default output" output rule markers
   102  	for ruleName, rule := range allOutputRules {
   103  		ruleMarker := markers.Must(markers.MakeDefinition("output:"+ruleName, markers.DescribesPackage, rule))
   104  		if err := optionsRegistry.Register(ruleMarker); err != nil {
   105  			panic(err)
   106  		}
   107  		if helpGiver, hasHelp := rule.(genall.HasHelp); hasHelp {
   108  			if help := helpGiver.Help(); help != nil {
   109  				optionsRegistry.AddHelp(ruleMarker, help)
   110  			}
   111  		}
   112  	}
   113  
   114  	// add in the common options markers
   115  	if err := genall.RegisterOptionsMarkers(optionsRegistry); err != nil {
   116  		panic(err)
   117  	}
   118  }
   119  
   120  // noUsageError suppresses usage printing when it occurs
   121  // (since cobra doesn't provide a good way to avoid printing
   122  // out usage in only certain situations).
   123  type noUsageError struct{ error }
   124  
   125  func main() {
   126  	helpLevel := 0
   127  	whichLevel := 0
   128  	showVersion := false
   129  
   130  	cmd := &cobra.Command{
   131  		Use:   "controller-gen",
   132  		Short: "Generate Kubernetes API extension resources and code.",
   133  		Long:  "Generate Kubernetes API extension resources and code.",
   134  		Example: `	# Generate RBAC manifests and crds for all types under apis/,
   135  	# outputting crds to /tmp/crds and everything else to stdout
   136  	controller-gen rbac:roleName=<role name> crd paths=./apis/... output:crd:dir=/tmp/crds output:stdout
   137  
   138  	# Generate deepcopy/runtime.Object implementations for a particular file
   139  	controller-gen object paths=./apis/v1beta1/some_types.go
   140  
   141  	# Generate OpenAPI v3 schemas for API packages and merge them into existing CRD manifests
   142  	controller-gen schemapatch:manifests=./manifests output:dir=./manifests paths=./pkg/apis/... 
   143  
   144  	# Run all the generators for a given project
   145  	controller-gen paths=./apis/...
   146  
   147  	# Explain the markers for generating CRDs, and their arguments
   148  	controller-gen crd -ww
   149  `,
   150  		RunE: func(c *cobra.Command, rawOpts []string) error {
   151  			// print version if asked for it
   152  			if showVersion {
   153  				version.Print()
   154  				return nil
   155  			}
   156  
   157  			// print the help if we asked for it (since we've got a different help flag :-/), then bail
   158  			if helpLevel > 0 {
   159  				return c.Usage()
   160  			}
   161  
   162  			// print the marker docs if we asked for them, then bail
   163  			if whichLevel > 0 {
   164  				return printMarkerDocs(c, rawOpts, whichLevel)
   165  			}
   166  
   167  			// otherwise, set up the runtime for actually running the generators
   168  			rt, err := genall.FromOptions(optionsRegistry, rawOpts)
   169  			if err != nil {
   170  				return err
   171  			}
   172  			if len(rt.Generators) == 0 {
   173  				return fmt.Errorf("no generators specified")
   174  			}
   175  
   176  			if hadErrs := rt.Run(); hadErrs {
   177  				// don't obscure the actual error with a bunch of usage
   178  				return noUsageError{fmt.Errorf("not all generators ran successfully")}
   179  			}
   180  			return nil
   181  		},
   182  		SilenceUsage: true, // silence the usage, then print it out ourselves if it wasn't suppressed
   183  	}
   184  	cmd.Flags().CountVarP(&whichLevel, "which-markers", "w", "print out all markers available with the requested generators\n(up to -www for the most detailed output, or -wwww for json output)")
   185  	cmd.Flags().CountVarP(&helpLevel, "detailed-help", "h", "print out more detailed help\n(up to -hhh for the most detailed output, or -hhhh for json output)")
   186  	cmd.Flags().BoolVar(&showVersion, "version", false, "show version")
   187  	cmd.Flags().Bool("help", false, "print out usage and a summary of options")
   188  	oldUsage := cmd.UsageFunc()
   189  	cmd.SetUsageFunc(func(c *cobra.Command) error {
   190  		if err := oldUsage(c); err != nil {
   191  			return err
   192  		}
   193  		if helpLevel == 0 {
   194  			helpLevel = summaryHelp
   195  		}
   196  		fmt.Fprintf(c.OutOrStderr(), "\n\nOptions\n\n")
   197  		return helpForLevels(c.OutOrStdout(), c.OutOrStderr(), helpLevel, optionsRegistry, help.SortByOption)
   198  	})
   199  
   200  	if err := cmd.Execute(); err != nil {
   201  		if _, noUsage := err.(noUsageError); !noUsage {
   202  			// print the usage unless we suppressed it
   203  			if err := cmd.Usage(); err != nil {
   204  				panic(err)
   205  			}
   206  		}
   207  		fmt.Fprintf(cmd.OutOrStderr(), "run `%[1]s %[2]s -w` to see all available markers, or `%[1]s %[2]s -h` for usage\n", cmd.CalledAs(), strings.Join(os.Args[1:], " "))
   208  		os.Exit(1)
   209  	}
   210  }
   211  
   212  // printMarkerDocs prints out marker help for the given generators specified in
   213  // the rawOptions, at the given level.
   214  func printMarkerDocs(c *cobra.Command, rawOptions []string, whichLevel int) error {
   215  	// just grab a registry so we don't lag while trying to load roots
   216  	// (like we'd do if we just constructed the full runtime).
   217  	reg, err := genall.RegistryFromOptions(optionsRegistry, rawOptions)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	return helpForLevels(c.OutOrStdout(), c.OutOrStderr(), whichLevel, reg, help.SortByCategory)
   223  }
   224  
   225  func helpForLevels(mainOut io.Writer, errOut io.Writer, whichLevel int, reg *markers.Registry, sorter help.SortGroup) error {
   226  	helpInfo := help.ByCategory(reg, sorter)
   227  	switch whichLevel {
   228  	case jsonHelp:
   229  		if err := json.NewEncoder(mainOut).Encode(helpInfo); err != nil {
   230  			return err
   231  		}
   232  	case detailedHelp, fullHelp:
   233  		fullDetail := whichLevel == fullHelp
   234  		for _, cat := range helpInfo {
   235  			if cat.Category == "" {
   236  				continue
   237  			}
   238  			contents := prettyhelp.MarkersDetails(fullDetail, cat.Category, cat.Markers)
   239  			if err := contents.WriteTo(errOut); err != nil {
   240  				return err
   241  			}
   242  		}
   243  	case summaryHelp:
   244  		for _, cat := range helpInfo {
   245  			if cat.Category == "" {
   246  				continue
   247  			}
   248  			contents := prettyhelp.MarkersSummary(cat.Category, cat.Markers)
   249  			if err := contents.WriteTo(errOut); err != nil {
   250  				return err
   251  			}
   252  		}
   253  	}
   254  	return nil
   255  }
   256  
   257  const (
   258  	_ = iota
   259  	summaryHelp
   260  	detailedHelp
   261  	fullHelp
   262  	jsonHelp
   263  )