Skip to content

Commit c81f98c

Browse files
authored
Create CRD validation specs in CRD manifests and as Go code (#869)
* commands/.../generate/*: build and call openapi-gen, and re-scaffold CRD manifests commands/.../add/api.go: call OpenAPI generator function pkg/scaffold/crd*: use CustomRenderer interface to write CRD manifests with validation spec instead of a template pkg/scaffold/gopkgtoml*: include openapi-gen deps pkg/scaffold/types*: add openapi-gen directives * fix comment * only use controller-tools CRD generator if in a Go project * change dir in unit test so Go project is detected * add names and only overwrite dstCrd when a file at path was generated remove status field from resulting struct * use generated crd if one isn't present locally * add test case for non-Go crd generation * internal/util/*util/*: remove pkg/scaffold imports, which cause cycles * test/e2e/memcached_test.go: symlink pkg and internal * pkg/scaffold/*: use a testData dir for scaffold tests instead of test/test-framework * rename test dir to avoid import cycles during e2e test * pkg/scaffold/crd.go: add IsOperatorGo field for controller-tools crd generator condition * use test/test-framework code as test project * revert removing scaffold constants from internal/util * revert SrcDir change and check IsOperatorGo first * change function name * pkg/scaffold/crd.go: simplify unmarshalling crd * use Golang initialism/acronym conventions for naming CR(D) types/variables * Gopkg.lock: revendor * commands/.../openapi.go: return error from CLI func instead of log.Fatal * fix kube-openapi revision * pkg/scaffold/gopkgtoml*.go: force sigs.k8s.io/controller-tools/pkg/crd/generator vendoring * remove extra kube-openapi override * pkg/scaffold/types*.go: add note on adding custom validation * commands/.../api.go: comment on what is generated in add api command usage * commands/.../openapi.go: remove go header file from openapi-gen * new() -> &...{} * correct verbosity settings for deepcopy and openapi generators * commands/operator-sdk/cmd/add/crd.go: only skip overwriting CRD's/CR's if explicitly adding them * verbose code generation in e2e test * add --header-file for Go boilerplate file path * update openapi command comment * override go-openapi/spec to avoid new project dep solve errors * revendor
1 parent afb05e9 commit c81f98c

File tree

23 files changed

+739
-163
lines changed

23 files changed

+739
-163
lines changed

Gopkg.lock

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

commands/operator-sdk/cmd/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ func NewAddCmd() *cobra.Command {
2929

3030
upCmd.AddCommand(add.NewApiCmd())
3131
upCmd.AddCommand(add.NewControllerCmd())
32-
upCmd.AddCommand(add.NewAddCrdCmd())
32+
upCmd.AddCommand(add.NewAddCRDCmd())
3333
return upCmd
3434
}

commands/operator-sdk/cmd/add/api.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,17 @@ func NewApiCmd() *cobra.Command {
3636
Use: "api",
3737
Short: "Adds a new api definition under pkg/apis",
3838
Long: `operator-sdk add api --kind=<kind> --api-version=<group/version> creates the
39-
api definition for a new custom resource under pkg/apis. This command must be run from the project root directory.
40-
If the api already exists at pkg/apis/<group>/<version> then the command will not overwrite and return an error.
39+
api definition for a new custom resource under pkg/apis. This command must be
40+
run from the project root directory. If the api already exists at
41+
pkg/apis/<group>/<version> then the command will not overwrite and return an
42+
error.
43+
44+
This command runs Kubernetes deepcopy and OpenAPI V3 generators on tagged
45+
types in all paths under pkg/apis. Go code is generated under
46+
pkg/apis/<group>/<version>/zz_generated.{deepcopy,openapi}.go. CRD's are
47+
generated, or updated if they exist for a particular group + version + kind,
48+
under deploy/crds/<group>_<version>_<kind>_crd.yaml; OpenAPI V3 validation YAML
49+
is generated as a 'validation' object.
4150
4251
Example:
4352
$ operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=AppService
@@ -49,8 +58,12 @@ Example:
4958
└── v1alpha1
5059
├── doc.go
5160
├── register.go
52-
├── types.go
53-
61+
├── appservice_types.go
62+
├── zz_generated.deepcopy.go
63+
├── zz_generated.openapi.go
64+
$ tree deploy/crds
65+
├── deploy/crds/app_v1alpha1_appservice_cr.yaml
66+
├── deploy/crds/app_v1alpha1_appservice_crd.yaml
5467
`,
5568
RunE: apiRun,
5669
}
@@ -96,8 +109,8 @@ func apiRun(cmd *cobra.Command, args []string) error {
96109
&scaffold.AddToScheme{Resource: r},
97110
&scaffold.Register{Resource: r},
98111
&scaffold.Doc{Resource: r},
99-
&scaffold.Cr{Resource: r},
100-
&scaffold.Crd{Resource: r},
112+
&scaffold.CR{Resource: r},
113+
&scaffold.CRD{Resource: r, IsOperatorGo: projutil.IsOperatorGo()},
101114
)
102115
if err != nil {
103116
return fmt.Errorf("api scaffold failed: (%v)", err)
@@ -113,6 +126,11 @@ func apiRun(cmd *cobra.Command, args []string) error {
113126
return err
114127
}
115128

129+
// Generate a validation spec for the new CRD.
130+
if err := generate.OpenAPIGen(); err != nil {
131+
return err
132+
}
133+
116134
log.Info("API generation complete.")
117135
return nil
118136
}

commands/operator-sdk/cmd/add/crd.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import (
2828
"github.com/spf13/cobra"
2929
)
3030

31-
// NewAddCrdCmd - add crd command
32-
func NewAddCrdCmd() *cobra.Command {
31+
// NewAddCRDCmd - add crd command
32+
func NewAddCRDCmd() *cobra.Command {
3333
crdCmd := &cobra.Command{
3434
Use: "crd",
3535
Short: "Adds a Custom Resource Definition (CRD) and the Custom Resource (CR) files",
@@ -78,11 +78,12 @@ func crdFunc(cmd *cobra.Command, args []string) error {
7878

7979
s := scaffold.Scaffold{}
8080
err = s.Execute(cfg,
81-
&scaffold.Crd{
82-
Input: input.Input{IfExistsAction: input.Skip},
83-
Resource: resource,
81+
&scaffold.CRD{
82+
Input: input.Input{IfExistsAction: input.Skip},
83+
Resource: resource,
84+
IsOperatorGo: projutil.IsOperatorGo(),
8485
},
85-
&scaffold.Cr{
86+
&scaffold.CR{
8687
Input: input.Input{IfExistsAction: input.Skip},
8788
Resource: resource,
8889
},

commands/operator-sdk/cmd/generate.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ func NewGenerateCmd() *cobra.Command {
2727
Long: `The operator-sdk generate command invokes specific generator to generate code as needed.`,
2828
}
2929
cmd.AddCommand(generate.NewGenerateK8SCmd())
30+
cmd.AddCommand(generate.NewGenerateOpenAPICmd())
3031
return cmd
3132
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2018 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 genutil
16+
17+
import (
18+
"fmt"
19+
"io/ioutil"
20+
"os"
21+
"os/exec"
22+
"path/filepath"
23+
"strings"
24+
25+
"github.com/operator-framework/operator-sdk/internal/util/projutil"
26+
"github.com/operator-framework/operator-sdk/pkg/scaffold"
27+
)
28+
29+
func BuildCodegenBinaries(genDirs []string, binDir, codegenSrcDir string) error {
30+
for _, gd := range genDirs {
31+
err := runGoBuildCodegen(binDir, codegenSrcDir, gd)
32+
if err != nil {
33+
return err
34+
}
35+
}
36+
return nil
37+
}
38+
39+
func runGoBuildCodegen(binDir, repoDir, genDir string) error {
40+
binPath := filepath.Join(binDir, filepath.Base(genDir))
41+
cmd := exec.Command("go", "build", "-o", binPath, genDir)
42+
cmd.Dir = repoDir
43+
if gf, ok := os.LookupEnv(projutil.GoFlagsEnv); ok && len(gf) != 0 {
44+
cmd.Env = append(os.Environ(), projutil.GoFlagsEnv+"="+gf)
45+
}
46+
47+
if projutil.IsGoVerbose() {
48+
return projutil.ExecCmd(cmd)
49+
}
50+
cmd.Stdout = ioutil.Discard
51+
cmd.Stderr = ioutil.Discard
52+
return cmd.Run()
53+
}
54+
55+
// ParseGroupVersions parses the layout of pkg/apis to return a map of
56+
// API groups to versions.
57+
func ParseGroupVersions() (map[string][]string, error) {
58+
gvs := make(map[string][]string)
59+
groups, err := ioutil.ReadDir(scaffold.ApisDir)
60+
if err != nil {
61+
return nil, fmt.Errorf("could not read pkg/apis directory to find api Versions: %v", err)
62+
}
63+
64+
for _, g := range groups {
65+
if g.IsDir() {
66+
groupDir := filepath.Join(scaffold.ApisDir, g.Name())
67+
versions, err := ioutil.ReadDir(groupDir)
68+
if err != nil {
69+
return nil, fmt.Errorf("could not read %s directory to find api Versions: %v", groupDir, err)
70+
}
71+
72+
gvs[g.Name()] = make([]string, 0)
73+
for _, v := range versions {
74+
if v.IsDir() && scaffold.ResourceVersionRegexp.MatchString(v.Name()) {
75+
gvs[g.Name()] = append(gvs[g.Name()], v.Name())
76+
}
77+
}
78+
}
79+
}
80+
81+
if len(gvs) == 0 {
82+
return nil, fmt.Errorf("no groups or versions found in %s", scaffold.ApisDir)
83+
}
84+
return gvs, nil
85+
}
86+
87+
// CreateFQApis return a string of all fully qualified pkg + groups + versions
88+
// of pkg and gvs in the format:
89+
// "pkg/groupA/v1,pkg/groupA/v2,pkg/groupB/v1"
90+
func CreateFQApis(pkg string, gvs map[string][]string) string {
91+
gn := 0
92+
fqb := &strings.Builder{}
93+
for g, vs := range gvs {
94+
for vn, v := range vs {
95+
fqb.WriteString(filepath.Join(pkg, g, v))
96+
if vn < len(vs)-1 {
97+
fqb.WriteString(",")
98+
}
99+
}
100+
if gn < len(gvs)-1 {
101+
fqb.WriteString(",")
102+
}
103+
gn++
104+
}
105+
return fqb.String()
106+
}

0 commit comments

Comments
 (0)