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

     1  /*
     2  Copyright 2022 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 v4
    18  
    19  import (
    20  	"bufio"
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  
    25  	log "github.com/sirupsen/logrus"
    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/v4/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 = "cmd/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  
    79    nano api/v1beta1/frigate_types.go
    80  
    81    # Edit the Controller
    82    nano internal/controller/frigate/frigate_controller.go
    83  
    84    # Edit the Controller Test
    85    nano internal/controller/frigate/frigate_controller_test.go
    86  
    87    # Generate the manifests
    88    make manifests
    89  
    90    # Install CRDs into the Kubernetes cluster using kubectl apply
    91    make install
    92  
    93    # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config
    94    make run
    95  `, cliMeta.CommandName)
    96  }
    97  
    98  func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) {
    99  	fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files")
   100  
   101  	fs.BoolVar(&p.force, "force", false,
   102  		"attempt to create resource even if it already exists")
   103  
   104  	p.options = &goPlugin.Options{}
   105  
   106  	fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form")
   107  
   108  	fs.BoolVar(&p.options.DoAPI, "resource", true,
   109  		"if set, generate the resource without prompting the user")
   110  	p.resourceFlag = fs.Lookup("resource")
   111  	fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced")
   112  
   113  	fs.BoolVar(&p.options.DoController, "controller", true,
   114  		"if set, generate the controller without prompting the user")
   115  	p.controllerFlag = fs.Lookup("controller")
   116  }
   117  
   118  func (p *createAPISubcommand) InjectConfig(c config.Config) error {
   119  	p.config = c
   120  	// go/v4 no longer supports v1beta1 option
   121  	p.options.CRDVersion = defaultCRDVersion
   122  	return nil
   123  }
   124  
   125  func (p *createAPISubcommand) InjectResource(res *resource.Resource) error {
   126  	p.resource = res
   127  
   128  	// TODO: re-evaluate whether y/n input still makes sense. We should probably always
   129  	//       scaffold the resource and controller.
   130  	// Ask for API and Controller if not specified
   131  	reader := bufio.NewReader(os.Stdin)
   132  	if !p.resourceFlag.Changed {
   133  		log.Println("Create Resource [y/n]")
   134  		p.options.DoAPI = util.YesNo(reader)
   135  	}
   136  	if !p.controllerFlag.Changed {
   137  		log.Println("Create Controller [y/n]")
   138  		p.options.DoController = util.YesNo(reader)
   139  	}
   140  
   141  	p.options.UpdateResource(p.resource, p.config)
   142  
   143  	if err := p.resource.Validate(); err != nil {
   144  		return err
   145  	}
   146  
   147  	// In case we want to scaffold a resource API we need to do some checks
   148  	if p.options.DoAPI {
   149  		// Check that resource doesn't have the API scaffolded or flag force was set
   150  		if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force {
   151  			return errors.New("API resource already exists")
   152  		}
   153  
   154  		// Check that the provided group can be added to the project
   155  		if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) {
   156  			return fmt.Errorf("multiple groups are not allowed by default, " +
   157  				"to enable multi-group visit https://kubebuilder.io/migration/multi-group.html")
   158  		}
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error {
   165  	// check if main.go is present in the root directory
   166  	if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) {
   167  		return fmt.Errorf("%s file should present in the root directory", DefaultMainPath)
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error {
   174  	scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force)
   175  	scaffolder.InjectFS(fs)
   176  	return scaffolder.Scaffold()
   177  }
   178  
   179  func (p *createAPISubcommand) PostScaffold() error {
   180  	err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
   181  	if err != nil {
   182  		return err
   183  	}
   184  	if p.runMake && p.resource.HasAPI() {
   185  		err = util.RunCmd("Running make", "make", "generate")
   186  		if err != nil {
   187  			return err
   188  		}
   189  		fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n")
   190  	}
   191  
   192  	return nil
   193  }