forked from kubernetes-sigs/kubebuilder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinit.go
214 lines (181 loc) · 6.43 KB
/
init.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v4
import (
"fmt"
"os"
"path/filepath"
"strings"
"unicode"
log "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"sigs.k8s.io/kubebuilder/v4/pkg/config"
"sigs.k8s.io/kubebuilder/v4/pkg/machinery"
"sigs.k8s.io/kubebuilder/v4/pkg/plugin"
"sigs.k8s.io/kubebuilder/v4/pkg/plugin/util"
"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang"
"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4/scaffolds"
)
// Variables and function to check Go version requirements.
var (
goVerMin = golang.MustParse("go1.23.0")
goVerMax = golang.MustParse("go2.0alpha1")
)
var _ plugin.InitSubcommand = &initSubcommand{}
type initSubcommand struct {
config config.Config
// For help text.
commandName string
// boilerplate options
license string
owner string
// go config options
repo string
// flags
fetchDeps bool
skipGoVersionCheck bool
}
func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) {
p.commandName = cliMeta.CommandName
subcmdMeta.Description = `Initialize a new project including the following files:
- a "go.mod" with project dependencies
- a "PROJECT" file that stores project configuration
- a "Makefile" with several useful make targets for the project
- several YAML files for project deployment under the "config" directory
- a "cmd/main.go" file that creates the manager that will run the project controllers
`
subcmdMeta.Examples = fmt.Sprintf(` # Initialize a new project with your domain and name in copyright
%[1]s init --plugins go/v4 --domain example.org --owner "Your name"
# Initialize a new project defining a specific project version
%[1]s init --plugins go/v4 --project-version 3
`, cliMeta.CommandName)
}
func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) {
fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check",
false, "if specified, skip checking the Go version")
// dependency args
fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded")
// boilerplate args
fs.StringVar(&p.license, "license", "apache2",
"license to use to boilerplate, may be one of 'apache2', 'none'")
fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright")
// project args
fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+
"defaults to the go package of the current working directory.")
}
func (p *initSubcommand) InjectConfig(c config.Config) error {
p.config = c
// Try to guess repository if flag is not set.
if p.repo == "" {
repoPath, err := golang.FindCurrentRepo()
if err != nil {
return fmt.Errorf("error finding current repository: %w", err)
}
p.repo = repoPath
}
if err := p.config.SetRepository(p.repo); err != nil {
return fmt.Errorf("error setting repository: %w", err)
}
return nil
}
func (p *initSubcommand) PreScaffold(machinery.Filesystem) error {
// Ensure Go version is in the allowed range if check not turned off.
if !p.skipGoVersionCheck {
if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil {
return fmt.Errorf("error validating go version: %w", err)
}
}
// Check if the current directory has no files or directories which does not allow to init the project
return checkDir()
}
func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error {
scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner, p.commandName)
scaffolder.InjectFS(fs)
if err := scaffolder.Scaffold(); err != nil {
return fmt.Errorf("error scaffolding init plugin: %w", err)
}
if !p.fetchDeps {
log.Println("Skipping fetching dependencies.")
return nil
}
// Ensure that we are pinning controller-runtime version
// xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997
err := util.RunCmd("Get controller runtime", "go", "get",
"sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion)
if err != nil {
return fmt.Errorf("error getting controller-runtime version: %w", err)
}
return nil
}
func (p *initSubcommand) PostScaffold() error {
err := util.RunCmd("Update dependencies", "go", "mod", "tidy")
if err != nil {
return fmt.Errorf("error updating go dependencies: %w", err)
}
fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName)
return nil
}
// checkDir will return error if the current directory has files which are not allowed.
// Note that, it is expected that the directory to scaffold the project is cleaned.
// Otherwise, it might face issues to do the scaffold.
func checkDir() error {
err := filepath.Walk(".",
func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error walking path %q: %w", path, err)
}
// Allow directory trees starting with '.'
if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." {
return filepath.SkipDir
}
// Allow files starting with '.'
if strings.HasPrefix(info.Name(), ".") {
return nil
}
// Allow files ending with '.md' extension
if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() {
return nil
}
// Allow capitalized files except PROJECT
isCapitalized := true
for _, l := range info.Name() {
if !unicode.IsUpper(l) {
isCapitalized = false
break
}
}
if isCapitalized && info.Name() != "PROJECT" {
return nil
}
disallowedExtensions := []string{
".go",
".yaml",
".mod",
".sum",
}
// Deny files with .go or .yaml or .mod or .sum extensions
for _, ext := range disallowedExtensions {
if strings.HasSuffix(info.Name(), ext) {
return nil
}
}
// Do not allow any other file
return fmt.Errorf("target directory is not empty and contains a disallowed file %q. "+
"files with the following extensions [%s] are not allowed to avoid conflicts with the tooling",
path, strings.Join(disallowedExtensions, ", "))
})
if err != nil {
return fmt.Errorf("error walking directory: %w", err)
}
return nil
}