sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/plugins/golang/v3/init.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  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"unicode"
    26  
    27  	log "github.com/sirupsen/logrus"
    28  	"github.com/spf13/pflag"
    29  
    30  	"sigs.k8s.io/kubebuilder/v3/pkg/config"
    31  	"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
    32  	"sigs.k8s.io/kubebuilder/v3/pkg/plugin"
    33  	"sigs.k8s.io/kubebuilder/v3/pkg/plugin/util"
    34  	"sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang"
    35  	"sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds"
    36  )
    37  
    38  // Variables and function to check Go version requirements.
    39  var (
    40  	goVerMin = golang.MustParse("go1.19.0")
    41  	goVerMax = golang.MustParse("go2.0alpha1")
    42  )
    43  
    44  var _ plugin.InitSubcommand = &initSubcommand{}
    45  
    46  type initSubcommand struct {
    47  	config config.Config
    48  	// For help text.
    49  	commandName string
    50  
    51  	// boilerplate options
    52  	license string
    53  	owner   string
    54  
    55  	// go config options
    56  	repo string
    57  
    58  	// flags
    59  	fetchDeps          bool
    60  	skipGoVersionCheck bool
    61  }
    62  
    63  func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
    64  	p.commandName = cliMeta.CommandName
    65  
    66  	subcmdMeta.Description = `Initialize a new project including the following files:
    67    - a "go.mod" with project dependencies
    68    - a "PROJECT" file that stores project configuration
    69    - a "Makefile" with several useful make targets for the project
    70    - several YAML files for project deployment under the "config" directory
    71    - a "main.go" file that creates the manager that will run the project controllers
    72  `
    73  	subcmdMeta.Examples = fmt.Sprintf(`  # Initialize a new project with your domain and name in copyright
    74    %[1]s init --plugins go/v3 --domain example.org --owner "Your name"
    75  
    76    # Initialize a new project defining a specific project version
    77    %[1]s init --plugins go/v3 --project-version 3
    78  `, cliMeta.CommandName)
    79  }
    80  
    81  func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {
    82  	fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check",
    83  		false, "if specified, skip checking the Go version")
    84  
    85  	// dependency args
    86  	fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded")
    87  
    88  	// boilerplate args
    89  	fs.StringVar(&p.license, "license", "apache2",
    90  		"license to use to boilerplate, may be one of 'apache2', 'none'")
    91  	fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright")
    92  
    93  	// project args
    94  	fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+
    95  		"defaults to the go package of the current working directory.")
    96  }
    97  
    98  func (p *initSubcommand) InjectConfig(c config.Config) error {
    99  	p.config = c
   100  
   101  	// Try to guess repository if flag is not set.
   102  	if p.repo == "" {
   103  		repoPath, err := golang.FindCurrentRepo()
   104  		if err != nil {
   105  			return fmt.Errorf("error finding current repository: %v", err)
   106  		}
   107  		p.repo = repoPath
   108  	}
   109  
   110  	return p.config.SetRepository(p.repo)
   111  }
   112  
   113  func (p *initSubcommand) PreScaffold(machinery.Filesystem) error {
   114  	// Ensure Go version is in the allowed range if check not turned off.
   115  	if !p.skipGoVersionCheck {
   116  		if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil {
   117  			return err
   118  		}
   119  	}
   120  
   121  	// Check if the current directory has not files or directories which does not allow to init the project
   122  	return checkDir()
   123  }
   124  
   125  func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
   126  	scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner)
   127  	scaffolder.InjectFS(fs)
   128  	err := scaffolder.Scaffold()
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	if !p.fetchDeps {
   134  		log.Println("Skipping fetching dependencies.")
   135  		return nil
   136  	}
   137  
   138  	// Ensure that we are pinning controller-runtime version
   139  	// xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997
   140  	err = util.RunCmd("Get controller runtime", "go", "get",
   141  		"sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	return nil
   147  }
   148  
   149  func (p *initSubcommand) PostScaffold() error {
   150  	err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
   156  	return nil
   157  }
   158  
   159  // checkDir will return error if the current directory has files which are not allowed.
   160  // Note that, it is expected that the directory to scaffold the project is cleaned.
   161  // Otherwise, it might face issues to do the scaffold.
   162  func checkDir() error {
   163  	err := filepath.Walk(".",
   164  		func(path string, info os.FileInfo, err error) error {
   165  			if err != nil {
   166  				return err
   167  			}
   168  			// Allow directory trees starting with '.'
   169  			if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." {
   170  				return filepath.SkipDir
   171  			}
   172  			// Allow files starting with '.'
   173  			if strings.HasPrefix(info.Name(), ".") {
   174  				return nil
   175  			}
   176  			// Allow files ending with '.md' extension
   177  			if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() {
   178  				return nil
   179  			}
   180  			// Allow capitalized files except PROJECT
   181  			isCapitalized := true
   182  			for _, l := range info.Name() {
   183  				if !unicode.IsUpper(l) {
   184  					isCapitalized = false
   185  					break
   186  				}
   187  			}
   188  			if isCapitalized && info.Name() != "PROJECT" {
   189  				return nil
   190  			}
   191  			// Allow files in the following list
   192  			allowedFiles := []string{
   193  				"go.mod", // user might run `go mod init` instead of providing the `--flag` at init
   194  				"go.sum", // auto-generated file related to go.mod
   195  			}
   196  			for _, allowedFile := range allowedFiles {
   197  				if info.Name() == allowedFile {
   198  					return nil
   199  				}
   200  			}
   201  			// Do not allow any other file
   202  			return fmt.Errorf(
   203  				"target directory is not empty (only %s, files and directories with the prefix \".\", "+
   204  					"files with the suffix \".md\" or capitalized files name are allowed); "+
   205  					"found existing file %q", strings.Join(allowedFiles, ", "), path)
   206  		})
   207  	if err != nil {
   208  		return err
   209  	}
   210  	return nil
   211  }