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