Skip to content

Commit c447c45

Browse files
author
Eric Stroczynski
authored
cmd/operator-sdk: scaffold a boilerplate file for new and migrated projects (#1275)
* cmd/operator-sdk: scaffold a boilerplate file for new and migrated projects with --header-file, and use for generate commands * internal/pkg/scaffold: add boilerplate scaffold and logic, validate boilerplate text
1 parent a0782e3 commit c447c45

File tree

11 files changed

+248
-33
lines changed

11 files changed

+248
-33
lines changed

Diff for: cmd/operator-sdk/add/api.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
var (
3030
apiVersion string
3131
kind string
32-
headerFile string
3332
)
3433

3534
func newAddApiCmd() *cobra.Command {
@@ -77,7 +76,6 @@ Example:
7776
if err := apiCmd.MarkFlagRequired("kind"); err != nil {
7877
log.Fatalf("Failed to mark `kind` flag for `add api` subcommand as required")
7978
}
80-
apiCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated files.")
8179

8280
return apiCmd
8381
}
@@ -124,12 +122,12 @@ func apiRun(cmd *cobra.Command, args []string) error {
124122
}
125123

126124
// Run k8s codegen for deepcopy
127-
if err := genutil.K8sCodegen(headerFile); err != nil {
125+
if err := genutil.K8sCodegen(); err != nil {
128126
return err
129127
}
130128

131129
// Generate a validation spec for the new CRD.
132-
if err := genutil.OpenAPIGen(headerFile); err != nil {
130+
if err := genutil.OpenAPIGen(); err != nil {
133131
return err
134132
}
135133

Diff for: cmd/operator-sdk/generate/k8s.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
)
2525

2626
func newGenerateK8SCmd() *cobra.Command {
27-
k8sCmd := &cobra.Command{
27+
return &cobra.Command{
2828
Use: "k8s",
2929
Short: "Generates Kubernetes code for custom resource",
3030
Long: `k8s generator generates code for custom resources given the API
@@ -41,10 +41,6 @@ Example:
4141
`,
4242
RunE: k8sFunc,
4343
}
44-
45-
k8sCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated files.")
46-
47-
return k8sCmd
4844
}
4945

5046
func k8sFunc(cmd *cobra.Command, args []string) error {
@@ -57,5 +53,5 @@ func k8sFunc(cmd *cobra.Command, args []string) error {
5753
return err
5854
}
5955

60-
return genutil.K8sCodegen(headerFile)
56+
return genutil.K8sCodegen()
6157
}

Diff for: cmd/operator-sdk/generate/openapi.go

+2-8
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ import (
2121
"github.com/spf13/cobra"
2222
)
2323

24-
var headerFile string
25-
2624
func newGenerateOpenAPICmd() *cobra.Command {
27-
openAPICmd := &cobra.Command{
25+
return &cobra.Command{
2826
Use: "openapi",
2927
Short: "Generates OpenAPI specs for API's",
3028
Long: `generate openapi generates OpenAPI validation specs in Go from tagged types
@@ -47,16 +45,12 @@ Example:
4745
`,
4846
RunE: openAPIFunc,
4947
}
50-
51-
openAPICmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated files.")
52-
53-
return openAPICmd
5448
}
5549

5650
func openAPIFunc(cmd *cobra.Command, args []string) error {
5751
if len(args) != 0 {
5852
return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath())
5953
}
6054

61-
return genutil.OpenAPIGen(headerFile)
55+
return genutil.OpenAPIGen()
6256
}

Diff for: cmd/operator-sdk/internal/genutil/genutil.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,14 @@ func createFQApis(pkg string, gvs map[string][]string) string {
110110
return fqb.String()
111111
}
112112

113-
func withHeaderFile(hf string, f func(string) error) (err error) {
114-
if hf == "" {
115-
hf, err = createEmptyTmpFile()
116-
if err != nil {
113+
func withHeaderFile(f func(string) error) (err error) {
114+
i, err := (&scaffold.Boilerplate{}).GetInput()
115+
if err != nil {
116+
return err
117+
}
118+
hf := i.Path
119+
if _, err := os.Stat(hf); os.IsNotExist(err) {
120+
if hf, err = createEmptyTmpFile(); err != nil {
117121
return err
118122
}
119123
defer func() {

Diff for: cmd/operator-sdk/internal/genutil/k8s.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,8 @@ import (
2727
)
2828

2929
// K8sCodegen performs deepcopy code-generation for all custom resources under
30-
// pkg/apis. hf is a path to a header file containing text to add to generated
31-
// files.
32-
func K8sCodegen(hf string) error {
30+
// pkg/apis.
31+
func K8sCodegen() error {
3332
projutil.MustInProjectRoot()
3433

3534
wd := projutil.MustGetwd()
@@ -59,7 +58,7 @@ func K8sCodegen(hf string) error {
5958
log.Infof("Running deepcopy code-generation for Custom Resource group versions: [%v]\n", gvb.String())
6059

6160
fdc := func(a string) error { return deepcopyGen(binDir, repoPkg, a, gvMap) }
62-
if err = withHeaderFile(hf, fdc); err != nil {
61+
if err = withHeaderFile(fdc); err != nil {
6362
return err
6463
}
6564

Diff for: cmd/operator-sdk/internal/genutil/openapi.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ import (
2828
log "github.com/sirupsen/logrus"
2929
)
3030

31-
// OpenAPIGen generates OpenAPI validation specs for all CRD's in dirs. hf is
32-
// a path to a header file containing text to add to generated files.
33-
func OpenAPIGen(hf string) error {
31+
// OpenAPIGen generates OpenAPI validation specs for all CRD's in dirs.
32+
func OpenAPIGen() error {
3433
projutil.MustInProjectRoot()
3534

3635
absProjectPath := projutil.MustGetwd()
@@ -57,7 +56,7 @@ func OpenAPIGen(hf string) error {
5756
fqApiStr := createFQApis(apisPkg, gvMap)
5857
fqApis := strings.Split(fqApiStr, ",")
5958
f := func(a string) error { return openAPIGen(binDir, a, fqApis) }
60-
if err = withHeaderFile(hf, f); err != nil {
59+
if err = withHeaderFile(f); err != nil {
6160
return err
6261
}
6362

Diff for: cmd/operator-sdk/migrate/cmd.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import (
3030
"github.com/spf13/cobra"
3131
)
3232

33-
var depManager string
33+
var (
34+
depManager string
35+
headerFile string
36+
)
3437

3538
// NewCmd returns a command that will add source code to an existing non-go operator
3639
func NewCmd() *cobra.Command {
@@ -42,6 +45,7 @@ func NewCmd() *cobra.Command {
4245
}
4346

4447
newCmd.Flags().StringVar(&depManager, "dep-manager", "dep", `Dependency manager the new project will use (choices: "dep")`)
48+
newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt")
4549

4650
return newCmd
4751
}
@@ -72,7 +76,6 @@ func migrateAnsible() error {
7276
AbsProjectPath: wd,
7377
ProjectName: filepath.Base(wd),
7478
}
75-
s := &scaffold.Scaffold{}
7679

7780
dockerfile := ansible.DockerfileHybrid{
7881
Watches: true,
@@ -91,6 +94,15 @@ func migrateAnsible() error {
9194
return err
9295
}
9396

97+
s := &scaffold.Scaffold{}
98+
if headerFile != "" {
99+
err = s.Execute(cfg, &scaffold.Boilerplate{BoilerplateSrcPath: headerFile})
100+
if err != nil {
101+
return fmt.Errorf("boilerplate scaffold failed: (%v)", err)
102+
}
103+
s.BoilerplatePath = headerFile
104+
}
105+
94106
if err := scaffoldAnsibleDepManager(s, cfg); err != nil {
95107
return errors.Wrap(err, "migrate Ansible dependency manager file scaffold failed")
96108
}
@@ -124,6 +136,14 @@ func migrateHelm() error {
124136
}
125137

126138
s := &scaffold.Scaffold{}
139+
if headerFile != "" {
140+
err := s.Execute(cfg, &scaffold.Boilerplate{BoilerplateSrcPath: headerFile})
141+
if err != nil {
142+
return fmt.Errorf("boilerplate scaffold failed: (%v)", err)
143+
}
144+
s.BoilerplatePath = headerFile
145+
}
146+
127147
if err := scaffoldHelmDepManager(s, cfg); err != nil {
128148
return errors.Wrap(err, "migrate Helm dependency manager file scaffold failed")
129149
}

Diff for: cmd/operator-sdk/new/cmd.go

+10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ generates a skeletal app-operator application in $GOPATH/src/github.com/example.
5656
newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (choices: \"go\", \"ansible\" or \"helm\")")
5757
newCmd.Flags().StringVar(&depManager, "dep-manager", "dep", `Dependency manager the new project will use (choices: "dep")`)
5858
newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository")
59+
newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt")
5960
newCmd.Flags().BoolVar(&generatePlaybook, "generate-playbook", false, "Generate a playbook skeleton. (Only used for --type ansible)")
6061
newCmd.Flags().BoolVar(&isClusterScoped, "cluster-scoped", false, "Generate cluster-scoped resources instead of namespace-scoped")
6162

@@ -72,6 +73,7 @@ var (
7273
operatorType string
7374
projectName string
7475
depManager string
76+
headerFile string
7577
skipGit bool
7678
generatePlaybook bool
7779
isClusterScoped bool
@@ -152,6 +154,14 @@ func doGoScaffold() error {
152154
}
153155
s := &scaffold.Scaffold{}
154156

157+
if headerFile != "" {
158+
err := s.Execute(cfg, &scaffold.Boilerplate{BoilerplateSrcPath: headerFile})
159+
if err != nil {
160+
return fmt.Errorf("boilerplate scaffold failed: (%v)", err)
161+
}
162+
s.BoilerplatePath = headerFile
163+
}
164+
155165
var err error
156166
switch m := projutil.DepManagerType(depManager); m {
157167
case projutil.DepManagerDep:

Diff for: internal/pkg/scaffold/boilerplate_go_txt.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package scaffold
16+
17+
import (
18+
"io/ioutil"
19+
"path/filepath"
20+
21+
"github.com/operator-framework/operator-sdk/internal/pkg/scaffold/input"
22+
23+
"github.com/spf13/afero"
24+
)
25+
26+
const (
27+
BoilerplateFile = "boilerplate.go.txt"
28+
HackDir = "hack"
29+
)
30+
31+
type Boilerplate struct {
32+
input.Input
33+
34+
// BoilerplateSrcPath is the path to a file containing boilerplate text for
35+
// generated Go files.
36+
BoilerplateSrcPath string
37+
}
38+
39+
func (s *Boilerplate) GetInput() (input.Input, error) {
40+
if s.Path == "" {
41+
s.Path = filepath.Join(HackDir, BoilerplateFile)
42+
}
43+
return s.Input, nil
44+
}
45+
46+
var _ CustomRenderer = &Boilerplate{}
47+
48+
func (s *Boilerplate) SetFS(_ afero.Fs) {}
49+
50+
func (s *Boilerplate) CustomRender() ([]byte, error) {
51+
return ioutil.ReadFile(s.BoilerplateSrcPath)
52+
}

Diff for: internal/pkg/scaffold/boilerplate_go_txt_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package scaffold
16+
17+
import (
18+
"testing"
19+
20+
"github.com/operator-framework/operator-sdk/internal/util/diffutil"
21+
22+
"github.com/spf13/afero"
23+
)
24+
25+
func TestBoilerplate(t *testing.T) {
26+
s, buf := setupScaffoldAndWriter()
27+
s.Fs = afero.NewMemMapFs()
28+
f, err := afero.TempFile(s.Fs, "", "")
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
if _, err = f.Write([]byte(boilerplate)); err != nil {
33+
t.Fatal(err)
34+
}
35+
if err = f.Close(); err != nil {
36+
t.Fatal(err)
37+
}
38+
s.BoilerplatePath = f.Name()
39+
err = s.Execute(appConfig, &Apis{})
40+
if err != nil {
41+
t.Fatalf("Failed to execute the scaffold: (%v)", err)
42+
}
43+
44+
if boilerplateExp != buf.String() {
45+
diffs := diffutil.Diff(boilerplateExp, buf.String())
46+
t.Fatalf("Expected vs actual differs.\n%v", diffs)
47+
}
48+
}
49+
50+
const boilerplate = `// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ac
51+
// velit a lacus tempor accumsan sit amet eu velit. Mauris orci lectus,
52+
// rutrum vitae porttitor in, interdum nec mauris. Praesent porttitor
53+
// lectus a sem volutpat, ac fringilla magna fermentum. Donec a nibh
54+
// a urna fringilla eleifend. Curabitur vitae lorem nulla. Ut at risus
55+
// varius, blandit risus quis, porta tellus. Vivamus scelerisque turpis
56+
// quis viverra rhoncus. Aenean non arcu velit.
57+
`
58+
59+
const boilerplateExp = boilerplate + "\n" + apisExp

0 commit comments

Comments
 (0)