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 }