sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/plugins/golang/v3/api.go (about)

     1  /*
     2  Copyright 2020 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  //go:deprecated This package has been deprecated in favor of v4
    18  package v3
    19  
    20  import (
    21  	"bufio"
    22  	"errors"
    23  	"fmt"
    24  	"os"
    25  
    26  	"github.com/spf13/pflag"
    27  
    28  	"sigs.k8s.io/kubebuilder/v3/pkg/config"
    29  	"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
    30  	"sigs.k8s.io/kubebuilder/v3/pkg/model/resource"
    31  	"sigs.k8s.io/kubebuilder/v3/pkg/plugin"
    32  	"sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
    33  	goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
    34  	"sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
    35  )
    36  
    37  const (
    38  	// defaultCRDVersion is the default CRD API version to scaffold.
    39  	defaultCRDVersion = "v1"
    40  )
    41  
    42  // DefaultMainPath is default file path of main.go
    43  const DefaultMainPath = "main.go"
    44  
    45  var _ plugin.CreateAPISubcommand = &createAPISubcommand{}
    46  
    47  type createAPISubcommand struct {
    48  	config config.Config
    49  
    50  	options *goPlugin.Options
    51  
    52  	resource *resource.Resource
    53  
    54  	// Check if we have to scaffold resource and/or controller
    55  	resourceFlag   *pflag.Flag
    56  	controllerFlag *pflag.Flag
    57  
    58  	// force indicates that the resource should be created even if it already exists
    59  	force bool
    60  
    61  	// runMake indicates whether to run make or not after scaffolding APIs
    62  	runMake bool
    63  }
    64  
    65  func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
    66  	subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller.
    67  
    68  If information about whether the resource and controller should be scaffolded
    69  was not explicitly provided, it will prompt the user if they should be.
    70  
    71  After the scaffold is written, the dependencies will be updated and
    72  make generate will be run.
    73  `
    74  	subcmdMeta.Examples = fmt.Sprintf(`  # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate
    75    %[1]s create api --group ship --version v1beta1 --kind Frigate
    76  
    77    # Edit the API Scheme
    78    nano api/v1beta1/frigate_types.go
    79  
    80    # Edit the Controller
    81    nano controllers/frigate/frigate_controller.go
    82  
    83    # Edit the Controller Test
    84    nano controllers/frigate/frigate_controller_test.go
    85  
    86    # Generate the manifests
    87    make manifests
    88  
    89    # Install CRDs into the Kubernetes cluster using kubectl apply
    90    make install
    91  
    92    # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
    93    make run
    94  `, cliMeta.CommandName)
    95  }
    96  
    97  func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
    98  	fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
    99  
   100  	fs.BoolVar(&p.force, "force", false,
   101  		"attempt to create resource even if it already exists")
   102  
   103  	p.options = &goPlugin.Options{}
   104  
   105  	fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
   106  
   107  	fs.BoolVar(&p.options.DoAPI, "resource", true,
   108  		"if set, generate the resource without prompting the user")
   109  	p.resourceFlag = fs.Lookup("resource")
   110  	fs.StringVar(&p.options.CRDVersion, "crd-version", defaultCRDVersion,
   111  		"version of CustomResourceDefinition to scaffold. Options: [v1, v1beta1]")
   112  	fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")
   113  
   114  	fs.BoolVar(&p.options.DoController, "controller", true,
   115  		"if set, generate the controller without prompting the user")
   116  	p.controllerFlag = fs.Lookup("controller")
   117  
   118  	// (not required raise an error in this case)
   119  	// nolint:errcheck,gosec
   120  	fs.MarkDeprecated("crd-version", deprecateMsg)
   121  }
   122  
   123  func (p *createAPISubcommand) InjectConfig(c config.Config) error {
   124  	p.config = c
   125  
   126  	return nil
   127  }
   128  
   129  func (p *createAPISubcommand) InjectResource(res *resource.Resource) error {
   130  	p.resource = res
   131  
   132  	// TODO: re-evaluate whether y/n input still makes sense. We should probably always
   133  	//       scaffold the resource and controller.
   134  	// Ask for API and Controller if not specified
   135  	reader := bufio.NewReader(os.Stdin)
   136  	if !p.resourceFlag.Changed {
   137  		fmt.Println("Create Resource [y/n]")
   138  		p.options.DoAPI = util.YesNo(reader)
   139  	}
   140  	if !p.controllerFlag.Changed {
   141  		fmt.Println("Create Controller [y/n]")
   142  		p.options.DoController = util.YesNo(reader)
   143  	}
   144  
   145  	p.options.UpdateResource(p.resource, p.config)
   146  
   147  	if err := p.resource.Validate(); err != nil {
   148  		return err
   149  	}
   150  
   151  	// In case we want to scaffold a resource API we need to do some checks
   152  	if p.options.DoAPI {
   153  		// Check that resource doesn't have the API scaffolded or flag force was set
   154  		if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force {
   155  			return errors.New("API resource already exists")
   156  		}
   157  
   158  		// Check that the provided group can be added to the project
   159  		if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {
   160  			return fmt.Errorf("multiple groups are not allowed by default, " +
   161  				"to enable multi-group visit https://kubebuilder.io/migration/multi-group.html")
   162  		}
   163  
   164  		// Check CRDVersion against all other CRDVersions in p.config for compatibility.
   165  		// nolint:staticcheck
   166  		if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) {
   167  			return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q",
   168  				p.resource.API.CRDVersion)
   169  		}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {
   176  	// check if main.go is present in the root directory
   177  	if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) {
   178  		return fmt.Errorf("%s file should present in the root directory", DefaultMainPath)
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {
   185  	scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)
   186  	scaffolder.InjectFS(fs)
   187  	return scaffolder.Scaffold()
   188  }
   189  
   190  func (p *createAPISubcommand) PostScaffold() error {
   191  
   192  	// Update the makefile to allow generate Webhooks to ensure backwards compatibility
   193  	// todo: it should be removed for go/v4
   194  	// nolint:lll,gosec
   195  	if p.resource.API.CRDVersion == "v1beta1" {
   196  		if err := applyScaffoldCustomizationsForVbeta1(); err != nil {
   197  			return err
   198  		}
   199  	}
   200  
   201  	err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
   202  	if err != nil {
   203  		return err
   204  	}
   205  	if p.runMake && p.resource.HasAPI() {
   206  		err = util.RunCmd("Running make", "make", "generate")
   207  		if err != nil {
   208  			return err
   209  		}
   210  		fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n")
   211  	}
   212  
   213  	return nil
   214  }