From c8cccc89d8261244f598de1aeb64dd5dc2730738 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Dec 2018 12:01:10 -0800 Subject: [PATCH 01/34] 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 --- Gopkg.lock | 47 +++++ commands/operator-sdk/cmd/add/api.go | 3 + commands/operator-sdk/cmd/generate.go | 1 + .../cmd/generate/internal/genutil.go | 110 ++++++++++++ commands/operator-sdk/cmd/generate/k8s.go | 91 +--------- commands/operator-sdk/cmd/generate/openapi.go | 164 ++++++++++++++++++ pkg/scaffold/crd.go | 136 +++++++++++++-- pkg/scaffold/crd_test.go | 66 +++++-- pkg/scaffold/gopkgtoml.go | 10 +- pkg/scaffold/gopkgtoml_test.go | 10 +- pkg/scaffold/types.go | 2 + pkg/scaffold/types_test.go | 2 + 12 files changed, 528 insertions(+), 114 deletions(-) create mode 100644 commands/operator-sdk/cmd/generate/internal/genutil.go create mode 100644 commands/operator-sdk/cmd/generate/openapi.go diff --git a/Gopkg.lock b/Gopkg.lock index 6db0069214..8a2c1082a5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -566,6 +566,14 @@ revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" version = "v2.0.1" +[[projects]] + digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "" + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" name = "github.com/pmezard/go-difflib" @@ -1165,6 +1173,20 @@ revision = "d082d5923d3cc0bfbb066ee5fbdea3d0ca79acf8" version = "kubernetes-1.12.3" +[[projects]] + branch = "master" + digest = "1:0cc7d194e005097afad585545a3049ac7b5d1e3a1bfa86aa6bf6f2fb0e0a5063" + name = "k8s.io/gengo" + packages = [ + "args", + "generator", + "namer", + "parser", + "types", + ] + pruneopts = "" + revision = "fd15ee9cc2f77baa4f31e59e6acbf21146455073" + [[projects]] digest = "1:f44f005adb4b22d8e110e125718014c7c3c650d93b9b62d30be1a5eba8267609" name = "k8s.io/helm" @@ -1196,6 +1218,14 @@ revision = "d325d2a9c179b33af1a024cdb5a4472b6288016a" version = "v2.12.0" +[[projects]] + digest = "1:4f5eb833037cc0ba0bf8fe9cae6be9df62c19dd1c869415275c708daa8ccfda5" + name = "k8s.io/klog" + packages = ["."] + pruneopts = "" + revision = "a5bc97fbc634d635061f3146511332c7e313a55a" + version = "v0.1.0" + [[projects]] digest = "1:a2f78b8fd86be41f2aa77404245aed4f4f410ac3aabc5f3bd9bd1fcc09076c53" name = "k8s.io/kube-openapi" @@ -1406,6 +1436,21 @@ revision = "c63ebda0bf4be5f0a8abd4003e4ea546032545ba" version = "v0.1.8" +[[projects]] + digest = "1:b44bf3e4adc37dfebcd1a3715d472509e0f4cf0ac24038cd7fc5654f176bd420" + name = "sigs.k8s.io/controller-tools" + packages = [ + "pkg/crd/generator", + "pkg/crd/util", + "pkg/internal/codegen", + "pkg/internal/codegen/parse", + "pkg/internal/general", + "pkg/util", + ] + pruneopts = "" + revision = "b072ef59824b16023b0e12c94d0040d99059a961" + version = "v0.1.7" + [[projects]] branch = "master" digest = "1:ef95cf7afffff0466e8fcfcb793cb2f76a60f3942dcfc16a56ce07473757a395" @@ -1439,6 +1484,7 @@ "k8s.io/api/apps/v1", "k8s.io/api/core/v1", "k8s.io/api/rbac/v1", + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme", "k8s.io/apimachinery/pkg/api/errors", "k8s.io/apimachinery/pkg/api/meta", @@ -1494,6 +1540,7 @@ "sigs.k8s.io/controller-runtime/pkg/runtime/scheme", "sigs.k8s.io/controller-runtime/pkg/runtime/signals", "sigs.k8s.io/controller-runtime/pkg/source", + "sigs.k8s.io/controller-tools/pkg/crd/generator", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/commands/operator-sdk/cmd/add/api.go b/commands/operator-sdk/cmd/add/api.go index b19d63e675..a90c4080d8 100644 --- a/commands/operator-sdk/cmd/add/api.go +++ b/commands/operator-sdk/cmd/add/api.go @@ -103,5 +103,8 @@ func apiRun(cmd *cobra.Command, args []string) { // Run k8s codegen for deepcopy generate.K8sCodegen() + // Generate a validation spec for the new CRD. + generate.OpenAPIGen() + log.Info("Api generation complete.") } diff --git a/commands/operator-sdk/cmd/generate.go b/commands/operator-sdk/cmd/generate.go index 39923f518f..53dbbdcf8c 100644 --- a/commands/operator-sdk/cmd/generate.go +++ b/commands/operator-sdk/cmd/generate.go @@ -27,5 +27,6 @@ func NewGenerateCmd() *cobra.Command { Long: `The operator-sdk generate command invokes specific generator to generate code as needed.`, } cmd.AddCommand(generate.NewGenerateK8SCmd()) + cmd.AddCommand(generate.NewGenerateOpenAPICmd()) return cmd } diff --git a/commands/operator-sdk/cmd/generate/internal/genutil.go b/commands/operator-sdk/cmd/generate/internal/genutil.go new file mode 100644 index 0000000000..a284152b69 --- /dev/null +++ b/commands/operator-sdk/cmd/generate/internal/genutil.go @@ -0,0 +1,110 @@ +// Copyright 2018 The Operator-SDK 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 genutil + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/operator-framework/operator-sdk/pkg/scaffold" +) + +func BuildCodegenBinaries(genDirs []string, binDir, codegenSrcDir string) error { + for _, gd := range genDirs { + err := runGoBuildCodegen(binDir, codegenSrcDir, gd) + if err != nil { + return err + } + } + return nil +} + +func runGoBuildCodegen(binDir, repoDir, genDir string) error { + binPath := filepath.Join(binDir, filepath.Base(genDir)) + installCmd := exec.Command("go", "build", "-o", binPath, genDir) + installCmd.Dir = repoDir + isVerbose := false + if gf, ok := os.LookupEnv("GOFLAGS"); ok && len(gf) != 0 { + installCmd.Env = append(os.Environ(), "GOFLAGS="+gf) + if strings.Contains(gf, "-v") { + isVerbose = true + } + } + if isVerbose { + installCmd.Stdout = os.Stdout + installCmd.Stderr = os.Stderr + } else { + installCmd.Stdout = ioutil.Discard + installCmd.Stderr = ioutil.Discard + } + return installCmd.Run() +} + +// ParseGroupVersions parses the layout of pkg/apis to return a map of +// API groups to versions. +func ParseGroupVersions() (map[string][]string, error) { + gvs := make(map[string][]string) + groups, err := ioutil.ReadDir(scaffold.ApisDir) + if err != nil { + return nil, fmt.Errorf("could not read pkg/apis directory to find api Versions: %v", err) + } + + for _, g := range groups { + if g.IsDir() { + groupDir := filepath.Join(scaffold.ApisDir, g.Name()) + versions, err := ioutil.ReadDir(groupDir) + if err != nil { + return nil, fmt.Errorf("could not read %s directory to find api Versions: %v", groupDir, err) + } + + gvs[g.Name()] = make([]string, 0) + for _, v := range versions { + if v.IsDir() && scaffold.ResourceVersionRegexp.MatchString(v.Name()) { + gvs[g.Name()] = append(gvs[g.Name()], v.Name()) + } + } + } + } + + if len(gvs) == 0 { + return nil, fmt.Errorf("no groups or versions found in %s", scaffold.ApisDir) + } + return gvs, nil +} + +// CreateFQApis return a string of all fully qualified pkg + groups + versions +// of pkg and gvs in the format: +// "pkg/groupA/v1,pkg/groupA/v2,pkg/groupB/v1" +func CreateFQApis(pkg string, gvs map[string][]string) string { + gn := 0 + fqb := &strings.Builder{} + for g, vs := range gvs { + for vn, v := range vs { + fqb.WriteString(filepath.Join(pkg, g, v)) + if vn < len(vs)-1 { + fqb.WriteString(",") + } + } + if gn < len(gvs)-1 { + fqb.WriteString(",") + } + gn++ + } + return fqb.String() +} diff --git a/commands/operator-sdk/cmd/generate/k8s.go b/commands/operator-sdk/cmd/generate/k8s.go index 9b2ef648b1..4308ab4a22 100644 --- a/commands/operator-sdk/cmd/generate/k8s.go +++ b/commands/operator-sdk/cmd/generate/k8s.go @@ -16,12 +16,11 @@ package generate import ( "fmt" - "io/ioutil" - "os" "os/exec" "path/filepath" "strings" + genutil "github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/generate/internal" "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/pkg/scaffold" @@ -62,7 +61,7 @@ func K8sCodegen() { buildCodegenBinaries(binDir, srcDir) - gvMap, err := parseGroupVersions() + gvMap, err := genutil.ParseGroupVersions() if err != nil { log.Fatalf("failed to parse group versions: (%v)", err) } @@ -71,7 +70,7 @@ func K8sCodegen() { gvb.WriteString(fmt.Sprintf("%s:%v, ", g, vs)) } - log.Infof("Running code-generation for Custom Resource group versions: [%v]\n", gvb.String()) + log.Infof("Running deepcopy code-generation for Custom Resource group versions: [%v]\n", gvb.String()) deepcopyGen(binDir, repoPkg, gvMap) @@ -86,93 +85,17 @@ func buildCodegenBinaries(binDir, codegenSrcDir string) { "./cmd/informer-gen", "./cmd/deepcopy-gen", } - for _, gd := range genDirs { - err := runGoBuildCodegen(binDir, codegenSrcDir, gd) - if err != nil { - log.Fatal(err) - } - } -} - -func runGoBuildCodegen(binDir, repoDir, genDir string) error { - binPath := filepath.Join(binDir, filepath.Base(genDir)) - installCmd := exec.Command("go", "build", "-o", binPath, genDir) - installCmd.Dir = repoDir - isVerbose := false - if gf, ok := os.LookupEnv("GOFLAGS"); ok && len(gf) != 0 { - installCmd.Env = append(os.Environ(), "GOFLAGS="+gf) - if strings.Contains(gf, "-v") { - isVerbose = true - } - } - if isVerbose { - installCmd.Stdout = os.Stdout - installCmd.Stderr = os.Stderr - } else { - installCmd.Stdout = ioutil.Discard - installCmd.Stderr = ioutil.Discard - } - return installCmd.Run() -} - -// parseGroupVersions parses the layout of pkg/apis to return a map of -// API groups to versions. -func parseGroupVersions() (map[string][]string, error) { - gvs := make(map[string][]string) - groups, err := ioutil.ReadDir(scaffold.ApisDir) + err := genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) if err != nil { - return nil, fmt.Errorf("could not read pkg/apis directory to find api Versions: %v", err) - } - - for _, g := range groups { - if g.IsDir() { - groupDir := filepath.Join(scaffold.ApisDir, g.Name()) - versions, err := ioutil.ReadDir(groupDir) - if err != nil { - return nil, fmt.Errorf("could not read %s directory to find api Versions: %v", groupDir, err) - } - - gvs[g.Name()] = make([]string, 0) - for _, v := range versions { - if v.IsDir() && scaffold.ResourceVersionRegexp.MatchString(v.Name()) { - gvs[g.Name()] = append(gvs[g.Name()], v.Name()) - } - } - } - } - - if len(gvs) == 0 { - return nil, fmt.Errorf("no groups or versions found in %s", scaffold.ApisDir) - } - return gvs, nil -} - -// createFQApis return a string of all fully qualified pkg + groups + versions -// of pkg and gvs in the format: -// "pkg/groupA/v1,pkg/groupA/v2,pkg/groupB/v1" -func createFQApis(pkg string, gvs map[string][]string) string { - gn := 0 - sb := &strings.Builder{} - for g, vs := range gvs { - for vn, v := range vs { - sb.WriteString(filepath.Join(pkg, g, v)) - if vn < len(vs)-1 { - sb.WriteString(",") - } - } - if gn < len(gvs)-1 { - sb.WriteString(",") - } - gn++ + log.Fatal(err) } - return sb.String() } func deepcopyGen(binDir, repoPkg string, gvMap map[string][]string) { apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) args := []string{ - "--input-dirs", createFQApis(apisPkg, gvMap), - "-O", "zz_generated.deepcopy", + "--input-dirs", genutil.CreateFQApis(apisPkg, gvMap), + "--output-file-base", "zz_generated.deepcopy", "--bounding-dirs", apisPkg, } cgPath := filepath.Join(binDir, "deepcopy-gen") diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go new file mode 100644 index 0000000000..6f439603dc --- /dev/null +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -0,0 +1,164 @@ +// Copyright 2018 The Operator-SDK 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 generate + +import ( + "fmt" + "io/ioutil" + "os/exec" + "path/filepath" + "strings" + + genutil "github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/generate/internal" + "github.com/operator-framework/operator-sdk/internal/util/projutil" + "github.com/operator-framework/operator-sdk/pkg/scaffold" + "github.com/operator-framework/operator-sdk/pkg/scaffold/input" + + "github.com/ghodss/yaml" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func NewGenerateOpenAPICmd() *cobra.Command { + openAPICmd := &cobra.Command{ + Use: "openapi", + Short: "Generates OpenAPI specs for API's", + Long: ` generate openapi generates OpenAPI validation specs in Go from types in +all pkg/apis// directories. +`, + Run: openAPIFunc, + } + + return openAPICmd +} + +func openAPIFunc(cmd *cobra.Command, args []string) { + if len(args) != 0 { + log.Fatal("openapi command doesn't accept any arguments") + } + + OpenAPIGen() +} + +// OpenAPIGen generates OpenAPI validation specs for all CRD's in dirs. +func OpenAPIGen() { + projutil.MustInProjectRoot() + + absProjectPath := projutil.MustGetwd() + repoPkg := projutil.CheckAndGetProjectGoPkg() + vendor := filepath.Join(absProjectPath, "vendor") + srcDir := filepath.Join(vendor, "k8s.io", "kube-openapi") + bpFile := filepath.Join(vendor, "k8s.io", "gengo", "boilerplate", "boilerplate.go.txt") + binDir := filepath.Join(absProjectPath, scaffold.BuildBinDir) + + buildOpenAPIGenBinary(binDir, srcDir) + + gvMap, err := genutil.ParseGroupVersions() + if err != nil { + log.Fatalf("failed to parse group versions: (%v)", err) + } + gvb := &strings.Builder{} + for g, vs := range gvMap { + gvb.WriteString(fmt.Sprintf("%s:%v, ", g, vs)) + } + + log.Infof("Running OpenAPI code-generation for Custom Resource group versions: [%v]\n", gvb.String()) + + apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) + fqApiStr := genutil.CreateFQApis(apisPkg, gvMap) + fqApis := strings.Split(fqApiStr, ",") + openAPIGen(binDir, bpFile, fqApis) + + s := &scaffold.Scaffold{} + cfg := &input.Config{ + Repo: repoPkg, + AbsProjectPath: absProjectPath, + ProjectName: filepath.Base(absProjectPath), + } + crdMap := getCrdGVKMap() + for g, vs := range gvMap { + for _, v := range vs { + gvks := crdMap[filepath.Join(g, v)] + for _, gvk := range gvks { + r, err := scaffold.NewResource(filepath.Join(gvk.Group, gvk.Version), gvk.Kind) + if err != nil { + log.Fatal(err) + } + if err = s.Execute(cfg, &scaffold.Crd{Resource: r}); err != nil { + log.Fatal(err) + } + } + } + } + + log.Info("Code-generation complete.") +} + +func buildOpenAPIGenBinary(binDir, codegenSrcDir string) { + genDirs := []string{"./cmd/openapi-gen"} + err := genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) + if err != nil { + log.Fatal(err) + } +} + +func openAPIGen(binDir, headerFile string, fqApis []string) { + cgPath := filepath.Join(binDir, "openapi-gen") + for _, fqApi := range fqApis { + args := []string{ + "--input-dirs", fqApi, + "--output-package", fqApi, + "--output-file-base", "zz_generated.openapi", + "--go-header-file", headerFile, + } + err := projutil.ExecCmd(exec.Command(cgPath, args...)) + if err != nil { + log.Fatalf("failed to perform code-generation: %v", err) + } + } +} + +func getCrdGVKMap() map[string][]metav1.GroupVersionKind { + crdInfos, err := ioutil.ReadDir(scaffold.CrdsDir) + if err != nil { + log.Fatal(err) + } + crdMap := make(map[string][]metav1.GroupVersionKind) + for _, info := range crdInfos { + if filepath.Ext(info.Name()) == ".yaml" { + path := filepath.Join(scaffold.CrdsDir, info.Name()) + b, err := ioutil.ReadFile(path) + if err != nil { + log.Fatal(err) + } + crd := new(apiextv1beta1.CustomResourceDefinition) + if err := yaml.Unmarshal(b, crd); err != nil { + log.Fatal(err) + } + if crd.Kind != "CustomResourceDefinition" { + continue + } + gv := filepath.Join(strings.Split(info.Name(), "_")[:2]...) + crdMap[gv] = append(crdMap[gv], metav1.GroupVersionKind{ + Group: crd.Spec.Group, + Version: crd.Spec.Version, + Kind: crd.Spec.Names.Kind, + }) + } + } + return crdMap +} diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index a1a386b86d..e2c08c517a 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -16,10 +16,19 @@ package scaffold import ( "fmt" + "io/ioutil" + "os" "path/filepath" "strings" + "sync" "github.com/operator-framework/operator-sdk/pkg/scaffold/input" + + "github.com/ghodss/yaml" + "github.com/spf13/afero" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + crdgenerator "sigs.k8s.io/controller-tools/pkg/crd/generator" ) // Crd is the input needed to generate a deploy/crds/___crd.yaml file @@ -38,24 +47,115 @@ func (s *Crd) GetInput() (input.Input, error) { s.Resource.LowerKind) s.Path = filepath.Join(CrdsDir, fileName) } - s.TemplateBody = crdTemplate + initCache() return s.Input, nil } -// TODO: Parse pkg/apis to generate CRD with open-api validation instead of using a static template -const crdTemplate = `apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: {{ .Resource.Resource }}.{{ .Resource.FullGroup }} -spec: - group: {{ .Resource.FullGroup }} - names: - kind: {{ .Resource.Kind }} - listKind: {{ .Resource.Kind }}List - plural: {{ .Resource.Resource }} - singular: {{ .Resource.LowerKind }} - scope: Namespaced - version: {{ .Resource.Version }} - subresources: - status: {} -` +type fsCache struct { + afero.Fs +} + +func (c *fsCache) fileExists(path string) bool { + _, err := c.Stat(path) + return err == nil +} + +var ( + // Global cache so users can use new Crd structs. + cache *fsCache + once sync.Once +) + +func initCache() { + once.Do(func() { + cache = &fsCache{Fs: afero.NewMemMapFs()} + }) +} + +func (s *Crd) CustomRender() ([]byte, error) { + i, _ := s.GetInput() + // controller-tools generates crd file names with no _crd.yaml suffix: + // __.yaml. + path := strings.Replace(filepath.Base(i.Path), "_crd.yaml", ".yaml", 1) + + // controller-tools' generators read and make crds for all apis in pkg/apis, + // so enerate crds in a cached, in-memory fs to extract the data we need. + // Note that controller-tools' generator makes different assumptions about + // how crd field values are structured, so we don't want to use the generated + // files directly. + if !cache.fileExists(path) { + g := &crdgenerator.Generator{ + RootPath: s.AbsProjectPath, + Domain: "placeholder", // Our crds don't use this value. + OutputDir: ".", + SkipMapValidation: false, + OutFs: cache, + } + if err := g.ValidateAndInitFields(); err != nil { + return nil, err + } + if err := g.Do(); err != nil { + return nil, err + } + } + + dstCrd := newCrdForResource(s.Resource) + var ( + b []byte + err error + ) + // Get our generated crd's from the in-memory fs. If it doesn't exist in the + // fs, the corresponding API does not exist yet, so scaffold a fresh crd + // without a validation spec. + // If it does, and a local crd exists, append the validation spec. Otherwise, + // generate a fresh crd with the generated validation spec. + b, err = afero.ReadFile(cache, path) + if err != nil && !os.IsNotExist(err) { + return nil, err + } else { + crd := new(apiextv1beta1.CustomResourceDefinition) + if err = yaml.Unmarshal(b, crd); err != nil { + return nil, err + } + + // If the crd exists at i.Path, append the validation spec to its crd spec. + if _, err := os.Stat(i.Path); err == nil { + cb, err := ioutil.ReadFile(i.Path) + if err != nil { + return nil, err + } + if len(cb) > 0 { + dstCrd = new(apiextv1beta1.CustomResourceDefinition) + if err = yaml.Unmarshal(cb, dstCrd); err != nil { + return nil, err + } + } + } + dstCrd.Spec.Validation = crd.Spec.Validation.DeepCopy() + } + + return yaml.Marshal(dstCrd) +} + +func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { + return &apiextv1beta1.CustomResourceDefinition{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apiextensions.k8s.io/v1beta1", + Kind: "CustomResourceDefinition", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: r.Resource + "." + r.FullGroup, + }, + Spec: apiextv1beta1.CustomResourceDefinitionSpec{ + Group: r.FullGroup, + Names: apiextv1beta1.CustomResourceDefinitionNames{ + Kind: r.Kind, + ListKind: r.Kind + "List", + Plural: r.Resource, + Singular: r.LowerKind, + }, + Scope: apiextv1beta1.NamespaceScoped, + Version: r.Version, + }, + } +} diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index a970bfd6c9..9a633dfe89 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -15,18 +15,33 @@ package scaffold import ( + "os" + "path/filepath" "testing" "github.com/operator-framework/operator-sdk/internal/util/diffutil" + "github.com/operator-framework/operator-sdk/pkg/scaffold/input" ) func TestCRD(t *testing.T) { - r, err := NewResource(appApiVersion, appKind) + r, err := NewResource("cache.example.com/v1alpha1", "Memcached") if err != nil { t.Fatal(err) } s, buf := setupScaffoldAndWriter() - err = s.Execute(appConfig, &Crd{Resource: r}) + absPath, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + // Set the project and repo paths to {abs}/test/test-framework, which + // contains pkg/apis for the memcached-operator. + tfDir := filepath.Join("test", "test-framework") + cfg := &input.Config{ + Repo: filepath.Join("github.com", "operator-framework", "operator-sdk", tfDir), + AbsProjectPath: filepath.Join(filepath.Dir(filepath.Dir(absPath)), tfDir), + ProjectName: filepath.Base(tfDir), + } + err = s.Execute(cfg, &Crd{Resource: r}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) } @@ -40,16 +55,47 @@ func TestCRD(t *testing.T) { const crdExp = `apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: appservices.app.example.com + creationTimestamp: null + name: memcacheds.cache.example.com spec: - group: app.example.com + group: cache.example.com names: - kind: AppService - listKind: AppServiceList - plural: appservices - singular: appservice + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached scope: Namespaced + validation: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + size: + format: int32 + type: integer + required: + - size + type: object + status: + properties: + nodes: + items: + type: string + type: array + required: + - nodes + type: object version: v1alpha1 - subresources: - status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null ` diff --git a/pkg/scaffold/gopkgtoml.go b/pkg/scaffold/gopkgtoml.go index 1da9438429..6d05151acb 100644 --- a/pkg/scaffold/gopkgtoml.go +++ b/pkg/scaffold/gopkgtoml.go @@ -49,7 +49,7 @@ required = [ "k8s.io/code-generator/cmd/client-gen", "k8s.io/code-generator/cmd/lister-gen", "k8s.io/code-generator/cmd/informer-gen", - "k8s.io/code-generator/cmd/openapi-gen", + "k8s.io/kube-openapi/cmd/openapi-gen", "k8s.io/gengo/args", ] @@ -58,6 +58,10 @@ required = [ # revision for tag "kubernetes-1.12.3" revision = "3dcf91f64f638563e5106f21f50c31fa361c918d" +[[override]] + name = "k8s.io/kube-openapi" + branch = "master" + [[override]] name = "k8s.io/api" # revision for tag "kubernetes-1.12.3" @@ -99,6 +103,10 @@ required = [ [[prune.project]] name = "k8s.io/code-generator" non-go = false + + [[prune.project]] + name = "k8s.io/gengo" + non-go = false ` func PrintDepsAsFile() { diff --git a/pkg/scaffold/gopkgtoml_test.go b/pkg/scaffold/gopkgtoml_test.go index 24669dcbee..0c4a37430d 100644 --- a/pkg/scaffold/gopkgtoml_test.go +++ b/pkg/scaffold/gopkgtoml_test.go @@ -41,7 +41,7 @@ required = [ "k8s.io/code-generator/cmd/client-gen", "k8s.io/code-generator/cmd/lister-gen", "k8s.io/code-generator/cmd/informer-gen", - "k8s.io/code-generator/cmd/openapi-gen", + "k8s.io/kube-openapi/cmd/openapi-gen", "k8s.io/gengo/args", ] @@ -50,6 +50,10 @@ required = [ # revision for tag "kubernetes-1.12.3" revision = "3dcf91f64f638563e5106f21f50c31fa361c918d" +[[override]] + name = "k8s.io/kube-openapi" + branch = "master" + [[override]] name = "k8s.io/api" # revision for tag "kubernetes-1.12.3" @@ -91,4 +95,8 @@ required = [ [[prune.project]] name = "k8s.io/code-generator" non-go = false + + [[prune.project]] + name = "k8s.io/gengo" + non-go = false ` diff --git a/pkg/scaffold/types.go b/pkg/scaffold/types.go index ccf5d7d436..91965f04e7 100644 --- a/pkg/scaffold/types.go +++ b/pkg/scaffold/types.go @@ -52,12 +52,14 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // {{.Resource.Kind}}Spec defines the desired state of {{.Resource.Kind}} +// +k8s:openapi-gen=true type {{.Resource.Kind}}Spec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file } // {{.Resource.Kind}}Status defines the observed state of {{.Resource.Kind}} +// +k8s:openapi-gen=true type {{.Resource.Kind}}Status struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file diff --git a/pkg/scaffold/types_test.go b/pkg/scaffold/types_test.go index f9e385170d..7a33a3ccc6 100644 --- a/pkg/scaffold/types_test.go +++ b/pkg/scaffold/types_test.go @@ -47,12 +47,14 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // AppServiceSpec defines the desired state of AppService +// +k8s:openapi-gen=true type AppServiceSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file } // AppServiceStatus defines the observed state of AppService +// +k8s:openapi-gen=true type AppServiceStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file From 0e5fb86dd30cc8208f4c985f200bc09a5acb0b7d Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Dec 2018 12:37:04 -0800 Subject: [PATCH 02/34] fix comment --- pkg/scaffold/crd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index e2c08c517a..f720ada92c 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -79,7 +79,7 @@ func (s *Crd) CustomRender() ([]byte, error) { path := strings.Replace(filepath.Base(i.Path), "_crd.yaml", ".yaml", 1) // controller-tools' generators read and make crds for all apis in pkg/apis, - // so enerate crds in a cached, in-memory fs to extract the data we need. + // so generate crds in a cached, in-memory fs to extract the data we need. // Note that controller-tools' generator makes different assumptions about // how crd field values are structured, so we don't want to use the generated // files directly. From 7db89b6a138d21ac6a820749be33f47e87034412 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Dec 2018 13:31:00 -0800 Subject: [PATCH 03/34] only use controller-tools CRD generator if in a Go project --- internal/util/projutil/project_util.go | 17 ++++++++++------- pkg/scaffold/crd.go | 5 +++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 3a7bf64758..a2faef27c0 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -21,16 +21,15 @@ import ( "path/filepath" "strings" - "github.com/operator-framework/operator-sdk/pkg/scaffold/ansible" - "github.com/operator-framework/operator-sdk/pkg/scaffold/helm" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) const ( - SrcDir = "src" + srcDir = "src" mainFile = "./cmd/manager/main.go" + rolesDir = "./roles" + helmChartsDir = "./helm-charts" buildDockerfile = "./build/Dockerfile" ) @@ -87,7 +86,7 @@ func MustGetwd() string { // e.g: "github.com/example-inc/app-operator" func CheckAndGetProjectGoPkg() string { gopath := SetGopath(GetGopath()) - goSrc := filepath.Join(gopath, SrcDir) + goSrc := filepath.Join(gopath, srcDir) wd := MustGetwd() currPkg := strings.Replace(wd, goSrc+string(filepath.Separator), "", 1) // strip any "/" prefix from the repo path. @@ -102,15 +101,19 @@ func GetOperatorType() OperatorType { if _, err := os.Stat(mainFile); err == nil { return OperatorTypeGo } - if stat, err := os.Stat(ansible.RolesDir); err == nil && stat.IsDir() { + if stat, err := os.Stat(rolesDir); err == nil && stat.IsDir() { return OperatorTypeAnsible } - if stat, err := os.Stat(helm.HelmChartsDir); err == nil && stat.IsDir() { + if stat, err := os.Stat(helmChartsDir); err == nil && stat.IsDir() { return OperatorTypeHelm } return OperatorTypeUnknown } +func IsOperatorGo() bool { + return GetOperatorType() == OperatorTypeGo +} + // GetGopath gets GOPATH and makes sure it is set and non-empty. func GetGopath() string { gopath, ok := os.LookupEnv(GopathEnv) diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index f720ada92c..086f95ba8a 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -22,6 +22,7 @@ import ( "strings" "sync" + "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/pkg/scaffold/input" "github.com/ghodss/yaml" @@ -82,8 +83,8 @@ func (s *Crd) CustomRender() ([]byte, error) { // so generate crds in a cached, in-memory fs to extract the data we need. // Note that controller-tools' generator makes different assumptions about // how crd field values are structured, so we don't want to use the generated - // files directly. - if !cache.fileExists(path) { + // files directly. This generator will fail if not in a Go project. + if !cache.fileExists(path) && projutil.IsOperatorGo() { g := &crdgenerator.Generator{ RootPath: s.AbsProjectPath, Domain: "placeholder", // Our crds don't use this value. From 82c09e24af9566b09c93a88386da1a8899398367 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Dec 2018 14:24:43 -0800 Subject: [PATCH 04/34] change dir in unit test so Go project is detected --- pkg/scaffold/crd_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index 9a633dfe89..fcfdebf95e 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -41,6 +41,9 @@ func TestCRD(t *testing.T) { AbsProjectPath: filepath.Join(filepath.Dir(filepath.Dir(absPath)), tfDir), ProjectName: filepath.Base(tfDir), } + if err := os.Chdir(cfg.AbsProjectPath); err != nil { + t.Fatal(err) + } err = s.Execute(cfg, &Crd{Resource: r}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) From cdb791c25fc8c96cf2a55f8daedfd29cbb1588bc Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Dec 2018 18:12:30 -0800 Subject: [PATCH 05/34] add names and only overwrite dstCrd when a file at path was generated remove status field from resulting struct --- pkg/scaffold/crd.go | 61 ++++++++++++++++++++++++++-------------- pkg/scaffold/crd_test.go | 8 ++---- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index 086f95ba8a..df3096fc87 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -81,13 +81,10 @@ func (s *Crd) CustomRender() ([]byte, error) { // controller-tools' generators read and make crds for all apis in pkg/apis, // so generate crds in a cached, in-memory fs to extract the data we need. - // Note that controller-tools' generator makes different assumptions about - // how crd field values are structured, so we don't want to use the generated - // files directly. This generator will fail if not in a Go project. if !cache.fileExists(path) && projutil.IsOperatorGo() { g := &crdgenerator.Generator{ RootPath: s.AbsProjectPath, - Domain: "placeholder", // Our crds don't use this value. + Domain: strings.SplitN(s.Resource.FullGroup, ".", 2)[1], OutputDir: ".", SkipMapValidation: false, OutFs: cache, @@ -101,19 +98,18 @@ func (s *Crd) CustomRender() ([]byte, error) { } dstCrd := newCrdForResource(s.Resource) - var ( - b []byte - err error - ) // Get our generated crd's from the in-memory fs. If it doesn't exist in the // fs, the corresponding API does not exist yet, so scaffold a fresh crd // without a validation spec. - // If it does, and a local crd exists, append the validation spec. Otherwise, - // generate a fresh crd with the generated validation spec. - b, err = afero.ReadFile(cache, path) - if err != nil && !os.IsNotExist(err) { + // If the crd exists in the fs, and a local crd exists, append the validation + // spec. If a local crd does not exist, use the generated crd. + if _, err := cache.Stat(path); err != nil && !os.IsNotExist(err) { return nil, err - } else { + } else if err == nil { + b, err := afero.ReadFile(cache, path) + if err != nil { + return nil, err + } crd := new(apiextv1beta1.CustomResourceDefinition) if err = yaml.Unmarshal(b, crd); err != nil { return nil, err @@ -133,9 +129,25 @@ func (s *Crd) CustomRender() ([]byte, error) { } } dstCrd.Spec.Validation = crd.Spec.Validation.DeepCopy() + // controller-tools does not set ListKind or Singular names. + dstCrd.Spec.Names = getCrdNamesForResource(s.Resource) + dstCrd.Spec.Subresources = &apiextv1beta1.CustomResourceSubresources{ + Status: &apiextv1beta1.CustomResourceSubresourceStatus{}, + } } - return yaml.Marshal(dstCrd) + b, err := yaml.Marshal(dstCrd) + if err != nil { + return nil, err + } + // Remove the "status" field from yaml data, which causes a + // resource creation error. + crdMap := make(map[string]interface{}) + if err = yaml.Unmarshal(b, &crdMap); err != nil { + return nil, err + } + delete(crdMap, "status") + return yaml.Marshal(&crdMap) } func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { @@ -148,15 +160,22 @@ func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { Name: r.Resource + "." + r.FullGroup, }, Spec: apiextv1beta1.CustomResourceDefinitionSpec{ - Group: r.FullGroup, - Names: apiextv1beta1.CustomResourceDefinitionNames{ - Kind: r.Kind, - ListKind: r.Kind + "List", - Plural: r.Resource, - Singular: r.LowerKind, - }, + Group: r.FullGroup, + Names: getCrdNamesForResource(r), Scope: apiextv1beta1.NamespaceScoped, Version: r.Version, + Subresources: &apiextv1beta1.CustomResourceSubresources{ + Status: &apiextv1beta1.CustomResourceSubresourceStatus{}, + }, }, } } + +func getCrdNamesForResource(r *Resource) apiextv1beta1.CustomResourceDefinitionNames { + return apiextv1beta1.CustomResourceDefinitionNames{ + Kind: r.Kind, + ListKind: r.Kind + "List", + Plural: r.Resource, + Singular: r.LowerKind, + } +} diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index fcfdebf95e..cafa9b1b07 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -68,6 +68,8 @@ spec: plural: memcacheds singular: memcached scope: Namespaced + subresources: + status: {} validation: openAPIV3Schema: properties: @@ -95,10 +97,4 @@ spec: - nodes type: object version: v1alpha1 -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ` From 2aeb74b1384cb501cfd8d450d5fb99c0634ce181 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 10:28:09 -0800 Subject: [PATCH 06/34] use generated crd if one isn't present locally --- pkg/scaffold/crd.go | 51 ++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index df3096fc87..d95823c817 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -110,10 +110,11 @@ func (s *Crd) CustomRender() ([]byte, error) { if err != nil { return nil, err } - crd := new(apiextv1beta1.CustomResourceDefinition) - if err = yaml.Unmarshal(b, crd); err != nil { + dstCrd = new(apiextv1beta1.CustomResourceDefinition) + if err = yaml.Unmarshal(b, dstCrd); err != nil { return nil, err } + val := dstCrd.Spec.Validation.DeepCopy() // If the crd exists at i.Path, append the validation spec to its crd spec. if _, err := os.Stat(i.Path); err == nil { @@ -126,28 +127,16 @@ func (s *Crd) CustomRender() ([]byte, error) { if err = yaml.Unmarshal(cb, dstCrd); err != nil { return nil, err } + dstCrd.Spec.Validation = val } } - dstCrd.Spec.Validation = crd.Spec.Validation.DeepCopy() // controller-tools does not set ListKind or Singular names. dstCrd.Spec.Names = getCrdNamesForResource(s.Resource) - dstCrd.Spec.Subresources = &apiextv1beta1.CustomResourceSubresources{ - Status: &apiextv1beta1.CustomResourceSubresourceStatus{}, - } - } - - b, err := yaml.Marshal(dstCrd) - if err != nil { - return nil, err + // Remove controller-tools default label. + delete(dstCrd.Labels, "controller-tools.k8s.io") } - // Remove the "status" field from yaml data, which causes a - // resource creation error. - crdMap := make(map[string]interface{}) - if err = yaml.Unmarshal(b, &crdMap); err != nil { - return nil, err - } - delete(crdMap, "status") - return yaml.Marshal(&crdMap) + addCrdStatus(dstCrd) + return getCrdBytes(dstCrd) } func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { @@ -179,3 +168,27 @@ func getCrdNamesForResource(r *Resource) apiextv1beta1.CustomResourceDefinitionN Singular: r.LowerKind, } } + +func addCrdStatus(crd *apiextv1beta1.CustomResourceDefinition) { + if crd.Spec.Subresources == nil { + crd.Spec.Subresources = &apiextv1beta1.CustomResourceSubresources{} + } + if crd.Spec.Subresources.Status == nil { + crd.Spec.Subresources.Status = &apiextv1beta1.CustomResourceSubresourceStatus{} + } +} + +func getCrdBytes(crd *apiextv1beta1.CustomResourceDefinition) ([]byte, error) { + b, err := yaml.Marshal(crd) + if err != nil { + return nil, err + } + // Remove the "status" field from yaml data, which causes a + // resource creation error. + crdMap := make(map[string]interface{}) + if err = yaml.Unmarshal(b, &crdMap); err != nil { + return nil, err + } + delete(crdMap, "status") + return yaml.Marshal(&crdMap) +} From e78a4839f9c83772c2c07162e50d0091674f0aa3 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 10:28:21 -0800 Subject: [PATCH 07/34] add test case for non-Go crd generation --- pkg/scaffold/crd_test.go | 44 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index cafa9b1b07..4ce1653ef1 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -23,7 +23,7 @@ import ( "github.com/operator-framework/operator-sdk/pkg/scaffold/input" ) -func TestCRD(t *testing.T) { +func TestCRDGoProject(t *testing.T) { r, err := NewResource("cache.example.com/v1alpha1", "Memcached") if err != nil { t.Fatal(err) @@ -44,18 +44,19 @@ func TestCRD(t *testing.T) { if err := os.Chdir(cfg.AbsProjectPath); err != nil { t.Fatal(err) } + defer func() { os.Chdir(absPath) }() err = s.Execute(cfg, &Crd{Resource: r}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) } - if crdExp != buf.String() { - diffs := diffutil.Diff(crdExp, buf.String()) + if crdGoExp != buf.String() { + diffs := diffutil.Diff(crdGoExp, buf.String()) t.Fatalf("expected vs actual differs.\n%v", diffs) } } -const crdExp = `apiVersion: apiextensions.k8s.io/v1beta1 +const crdGoExp = `apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: creationTimestamp: null @@ -98,3 +99,38 @@ spec: type: object version: v1alpha1 ` + +func TestCrdNonGoProject(t *testing.T) { + r, err := NewResource(appApiVersion, appKind) + if err != nil { + t.Fatal(err) + } + s, buf := setupScaffoldAndWriter() + err = s.Execute(appConfig, &Crd{Resource: r}) + if err != nil { + t.Fatalf("failed to execute the scaffold: (%v)", err) + } + + if crdNonGoExp != buf.String() { + diffs := diffutil.Diff(crdNonGoExp, buf.String()) + t.Fatalf("expected vs actual differs.\n%v", diffs) + } +} + +const crdNonGoExp = `apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + creationTimestamp: null + name: appservices.app.example.com +spec: + group: app.example.com + names: + kind: AppService + listKind: AppServiceList + plural: appservices + singular: appservice + scope: Namespaced + subresources: + status: {} + version: v1alpha1 +` From 03f39dea517d350983b757ee7a222f91fc27738b Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 10:51:46 -0800 Subject: [PATCH 08/34] internal/util/*util/*: remove pkg/scaffold imports, which cause cycles --- internal/util/yamlutil/manifest.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/util/yamlutil/manifest.go b/internal/util/yamlutil/manifest.go index 5b0df75203..a9ce9601f4 100644 --- a/internal/util/yamlutil/manifest.go +++ b/internal/util/yamlutil/manifest.go @@ -23,7 +23,6 @@ import ( "strings" "github.com/operator-framework/operator-sdk/internal/util/fileutil" - "github.com/operator-framework/operator-sdk/pkg/scaffold" log "github.com/sirupsen/logrus" ) @@ -51,6 +50,8 @@ func CombineManifests(base []byte, manifests ...[]byte) []byte { return base } +const deployDir = "deploy" + // GenerateCombinedNamespacedManifest creates a temporary manifest yaml // containing all standard namespaced resource manifests combined into 1 file func GenerateCombinedNamespacedManifest() (*os.File, error) { @@ -60,19 +61,19 @@ func GenerateCombinedNamespacedManifest() (*os.File, error) { } defer file.Close() - sa, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.ServiceAccountYamlFile)) + sa, err := ioutil.ReadFile(filepath.Join(deployDir, "service_account.yaml")) if err != nil { log.Warnf("could not find the serviceaccount manifest: (%v)", err) } - role, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.RoleYamlFile)) + role, err := ioutil.ReadFile(filepath.Join(deployDir, "role.yaml")) if err != nil { log.Warnf("could not find role manifest: (%v)", err) } - roleBinding, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.RoleBindingYamlFile)) + roleBinding, err := ioutil.ReadFile(filepath.Join(deployDir, "role_binding.yaml")) if err != nil { log.Warnf("could not find role_binding manifest: (%v)", err) } - operator, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.OperatorYamlFile)) + operator, err := ioutil.ReadFile(filepath.Join(deployDir, "operator.yaml")) if err != nil { return nil, fmt.Errorf("could not find operator manifest: (%v)", err) } @@ -100,16 +101,17 @@ func GenerateCombinedGlobalManifest() (*os.File, error) { } defer file.Close() - files, err := ioutil.ReadDir(scaffold.CrdsDir) + crdsDir := filepath.Join(deployDir, "crds") + files, err := ioutil.ReadDir(crdsDir) if err != nil { return nil, fmt.Errorf("could not read deploy directory: (%v)", err) } combined := []byte{} for _, file := range files { if strings.HasSuffix(file.Name(), "crd.yaml") { - fileBytes, err := ioutil.ReadFile(filepath.Join(scaffold.CrdsDir, file.Name())) + fileBytes, err := ioutil.ReadFile(filepath.Join(crdsDir, file.Name())) if err != nil { - return nil, fmt.Errorf("could not read file %s: (%v)", filepath.Join(scaffold.CrdsDir, file.Name()), err) + return nil, fmt.Errorf("could not read file %s: (%v)", filepath.Join(crdsDir, file.Name()), err) } combined = CombineManifests(combined, fileBytes) } From ac572ae72e5783fe4f629f9bc150882a876f90ee Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 10:52:23 -0800 Subject: [PATCH 09/34] test/e2e/memcached_test.go: symlink pkg and internal --- test/e2e/memcached_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 3f08aa6724..3c74872f2c 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -224,9 +224,12 @@ func TestMemcached(t *testing.T) { } // link local sdk to vendor if not in travis if repo == "" { - os.RemoveAll("vendor/github.com/operator-framework/operator-sdk/pkg") - os.Symlink(filepath.Join(gopath, "src/github.com/operator-framework/operator-sdk/pkg"), - "vendor/github.com/operator-framework/operator-sdk/pkg") + for _, dir := range []string{"pkg", "internal"} { + repoDir := filepath.Join("github.com/operator-framework/operator-sdk", dir) + vendorDir := filepath.Join("vendor", repoDir) + os.RemoveAll(vendorDir) + os.Symlink(filepath.Join(gopath, "src", repoDir), vendorDir) + } } file, err := yamlutil.GenerateCombinedGlobalManifest() From 39dd0f93a6f9ac89b3d0e8d91dd4ae33677bbd01 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 12:42:50 -0800 Subject: [PATCH 10/34] pkg/scaffold/*: use a testData dir for scaffold tests instead of test/test-framework --- pkg/scaffold/crd_test.go | 14 +- pkg/scaffold/testData/cmd/manager/main.go | 109 ++++++++ .../crds/cache_v1alpha1_memcached_cr.yaml | 7 + .../crds/cache_v1alpha1_memcached_crd.yaml | 13 + .../testData/deploy/namespace-init.yaml | 83 ++++++ pkg/scaffold/testData/deploy/operator.yaml | 32 +++ pkg/scaffold/testData/deploy/role.yaml | 33 +++ .../testData/deploy/role_binding.yaml | 11 + .../testData/deploy/service_account.yaml | 4 + pkg/scaffold/testData/deploy/test-pod.yaml | 16 ++ .../pkg/apis/addtoscheme_cache_v1alpha1.go | 24 ++ pkg/scaffold/testData/pkg/apis/apis.go | 27 ++ .../testData/pkg/apis/cache/v1alpha1/doc.go | 18 ++ .../apis/cache/v1alpha1/memcached_types.go | 59 +++++ .../pkg/apis/cache/v1alpha1/register.go | 33 +++ .../cache/v1alpha1/zz_generated.deepcopy.go | 107 ++++++++ .../testData/pkg/controller/add_memcached.go | 24 ++ .../testData/pkg/controller/controller.go | 32 +++ .../memcached/memcached_controller.go | 243 ++++++++++++++++++ 19 files changed, 883 insertions(+), 6 deletions(-) create mode 100644 pkg/scaffold/testData/cmd/manager/main.go create mode 100644 pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_cr.yaml create mode 100644 pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_crd.yaml create mode 100644 pkg/scaffold/testData/deploy/namespace-init.yaml create mode 100644 pkg/scaffold/testData/deploy/operator.yaml create mode 100644 pkg/scaffold/testData/deploy/role.yaml create mode 100644 pkg/scaffold/testData/deploy/role_binding.yaml create mode 100644 pkg/scaffold/testData/deploy/service_account.yaml create mode 100644 pkg/scaffold/testData/deploy/test-pod.yaml create mode 100644 pkg/scaffold/testData/pkg/apis/addtoscheme_cache_v1alpha1.go create mode 100644 pkg/scaffold/testData/pkg/apis/apis.go create mode 100644 pkg/scaffold/testData/pkg/apis/cache/v1alpha1/doc.go create mode 100644 pkg/scaffold/testData/pkg/apis/cache/v1alpha1/memcached_types.go create mode 100644 pkg/scaffold/testData/pkg/apis/cache/v1alpha1/register.go create mode 100644 pkg/scaffold/testData/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/scaffold/testData/pkg/controller/add_memcached.go create mode 100644 pkg/scaffold/testData/pkg/controller/controller.go create mode 100644 pkg/scaffold/testData/pkg/controller/memcached/memcached_controller.go diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index 4ce1653ef1..1da2464d31 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -17,6 +17,7 @@ package scaffold import ( "os" "path/filepath" + "strings" "testing" "github.com/operator-framework/operator-sdk/internal/util/diffutil" @@ -33,13 +34,14 @@ func TestCRDGoProject(t *testing.T) { if err != nil { t.Fatal(err) } - // Set the project and repo paths to {abs}/test/test-framework, which - // contains pkg/apis for the memcached-operator. - tfDir := filepath.Join("test", "test-framework") + // Set the project and repo paths to {abs}/testData, which contains pkg/apis + // for the memcached-operator. + td := "testData" + repo := absPath[strings.Index(absPath, "github.com"):] cfg := &input.Config{ - Repo: filepath.Join("github.com", "operator-framework", "operator-sdk", tfDir), - AbsProjectPath: filepath.Join(filepath.Dir(filepath.Dir(absPath)), tfDir), - ProjectName: filepath.Base(tfDir), + Repo: filepath.Join(repo, td), + AbsProjectPath: filepath.Join(absPath, td), + ProjectName: td, } if err := os.Chdir(cfg.AbsProjectPath); err != nil { t.Fatal(err) diff --git a/pkg/scaffold/testData/cmd/manager/main.go b/pkg/scaffold/testData/cmd/manager/main.go new file mode 100644 index 0000000000..4394fea566 --- /dev/null +++ b/pkg/scaffold/testData/cmd/manager/main.go @@ -0,0 +1,109 @@ +// Copyright 2018 The Operator-SDK 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 main + +import ( + "context" + "flag" + "fmt" + "os" + "runtime" + + "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/apis" + "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/controller" + + "github.com/operator-framework/operator-sdk/pkg/k8sutil" + "github.com/operator-framework/operator-sdk/pkg/leader" + "github.com/operator-framework/operator-sdk/pkg/ready" + sdkVersion "github.com/operator-framework/operator-sdk/version" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/runtime/signals" +) + +var log = logf.Log.WithName("cmd") + +func printVersion() { + log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) + log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) + log.Info(fmt.Sprintf("operator-sdk Version: %v", sdkVersion.Version)) +} + +func main() { + flag.Parse() + + // The logger instantiated here can be changed to any logger + // implementing the logr.Logger interface. This logger will + // be propagated through the whole operator, generating + // uniform and structured logs. + logf.SetLogger(logf.ZapLogger(false)) + + printVersion() + + namespace, err := k8sutil.GetWatchNamespace() + if err != nil { + log.Error(err, "failed to get watch namespace") + os.Exit(1) + } + + // Get a config to talk to the apiserver + cfg, err := config.GetConfig() + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + // Become the leader before proceeding + leader.Become(context.TODO(), "memcached-operator-lock") + + r := ready.NewFileReady() + err = r.Set() + if err != nil { + log.Error(err, "") + os.Exit(1) + } + defer r.Unset() + + // Create a new Cmd to provide shared dependencies and start components + mgr, err := manager.New(cfg, manager.Options{Namespace: namespace}) + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + log.Info("Registering Components.") + + // Setup Scheme for all resources + if err := apis.AddToScheme(mgr.GetScheme()); err != nil { + log.Error(err, "") + os.Exit(1) + } + + // Setup all Controllers + if err := controller.AddToManager(mgr); err != nil { + log.Error(err, "") + os.Exit(1) + } + + log.Info("Starting the Cmd.") + + // Start the Cmd + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + log.Error(err, "manager exited non-zero") + os.Exit(1) + } +} diff --git a/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_cr.yaml b/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_cr.yaml new file mode 100644 index 0000000000..2b8f17c399 --- /dev/null +++ b/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_cr.yaml @@ -0,0 +1,7 @@ +apiVersion: cache.example.com/v1alpha1 +kind: Memcached +metadata: + name: example-memcached +spec: + # Add fields here + size: 3 diff --git a/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_crd.yaml b/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_crd.yaml new file mode 100644 index 0000000000..ab3f99f18d --- /dev/null +++ b/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_crd.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: memcacheds.cache.example.com +spec: + group: cache.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + version: v1alpha1 diff --git a/pkg/scaffold/testData/deploy/namespace-init.yaml b/pkg/scaffold/testData/deploy/namespace-init.yaml new file mode 100644 index 0000000000..73b8147671 --- /dev/null +++ b/pkg/scaffold/testData/deploy/namespace-init.yaml @@ -0,0 +1,83 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: memcached-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: memcached-operator +rules: +- apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - '*' +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - cache.example.com + resources: + - '*' + verbs: + - '*' +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: memcached-operator +subjects: +- kind: ServiceAccount + name: memcached-operator +roleRef: + kind: Role + name: memcached-operator + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: memcached-operator +spec: + replicas: 1 + selector: + matchLabels: + name: memcached-operator + template: + metadata: + labels: + name: memcached-operator + spec: + serviceAccountName: memcached-operator + containers: + - name: memcached-operator + # Replace this with the built image name + image: quay.io/coreos/operator-sdk-dev:test-framework-operator-runtime + ports: + - containerPort: 60000 + name: metrics + command: + - memcached-operator + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "memcached-operator" diff --git a/pkg/scaffold/testData/deploy/operator.yaml b/pkg/scaffold/testData/deploy/operator.yaml new file mode 100644 index 0000000000..6552adc6e3 --- /dev/null +++ b/pkg/scaffold/testData/deploy/operator.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: memcached-operator +spec: + replicas: 1 + selector: + matchLabels: + name: memcached-operator + template: + metadata: + labels: + name: memcached-operator + spec: + serviceAccountName: memcached-operator + containers: + - name: memcached-operator + # Replace this with the built image name + image: quay.io/coreos/operator-sdk-dev:test-framework-operator-runtime + ports: + - containerPort: 60000 + name: metrics + command: + - memcached-operator + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "memcached-operator" diff --git a/pkg/scaffold/testData/deploy/role.yaml b/pkg/scaffold/testData/deploy/role.yaml new file mode 100644 index 0000000000..a3075d534d --- /dev/null +++ b/pkg/scaffold/testData/deploy/role.yaml @@ -0,0 +1,33 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: memcached-operator +rules: +- apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - '*' +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - cache.example.com + resources: + - '*' + verbs: + - '*' diff --git a/pkg/scaffold/testData/deploy/role_binding.yaml b/pkg/scaffold/testData/deploy/role_binding.yaml new file mode 100644 index 0000000000..322ecc9e6a --- /dev/null +++ b/pkg/scaffold/testData/deploy/role_binding.yaml @@ -0,0 +1,11 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: memcached-operator +subjects: +- kind: ServiceAccount + name: memcached-operator +roleRef: + kind: Role + name: memcached-operator + apiGroup: rbac.authorization.k8s.io diff --git a/pkg/scaffold/testData/deploy/service_account.yaml b/pkg/scaffold/testData/deploy/service_account.yaml new file mode 100644 index 0000000000..8d58bc7832 --- /dev/null +++ b/pkg/scaffold/testData/deploy/service_account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: memcached-operator diff --git a/pkg/scaffold/testData/deploy/test-pod.yaml b/pkg/scaffold/testData/deploy/test-pod.yaml new file mode 100644 index 0000000000..bb7e325158 --- /dev/null +++ b/pkg/scaffold/testData/deploy/test-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: -test +spec: + restartPolicy: Never + containers: + - name: -test + image: + imagePullPolicy: Always + command: ["/go-test.sh"] + env: + - name: + valueFrom: + fieldRef: + fieldPath: metadata.namespace diff --git a/pkg/scaffold/testData/pkg/apis/addtoscheme_cache_v1alpha1.go b/pkg/scaffold/testData/pkg/apis/addtoscheme_cache_v1alpha1.go new file mode 100644 index 0000000000..ca10efeee1 --- /dev/null +++ b/pkg/scaffold/testData/pkg/apis/addtoscheme_cache_v1alpha1.go @@ -0,0 +1,24 @@ +// Copyright 2018 The Operator-SDK 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 apis + +import ( + "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/apis/cache/v1alpha1" +) + +func init() { + // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back + AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) +} diff --git a/pkg/scaffold/testData/pkg/apis/apis.go b/pkg/scaffold/testData/pkg/apis/apis.go new file mode 100644 index 0000000000..6f79149168 --- /dev/null +++ b/pkg/scaffold/testData/pkg/apis/apis.go @@ -0,0 +1,27 @@ +// Copyright 2018 The Operator-SDK 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 apis + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// AddToSchemes may be used to add all resources defined in the project to a Scheme +var AddToSchemes runtime.SchemeBuilder + +// AddToScheme adds all Resources to the Scheme +func AddToScheme(s *runtime.Scheme) error { + return AddToSchemes.AddToScheme(s) +} diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/doc.go b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/doc.go new file mode 100644 index 0000000000..84fd6d207f --- /dev/null +++ b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/doc.go @@ -0,0 +1,18 @@ +// Copyright 2018 The Operator-SDK 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 v1alpha1 contains API Schema definitions for the cache v1alpha1 API group +// +k8s:deepcopy-gen=package,register +// +groupName=cache.example.com +package v1alpha1 diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/memcached_types.go b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/memcached_types.go new file mode 100644 index 0000000000..f66f9dc6d4 --- /dev/null +++ b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/memcached_types.go @@ -0,0 +1,59 @@ +// Copyright 2018 The Operator-SDK 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// +k8s:openapi-gen=true +type MemcachedSpec struct { + // Size is the size of the memcached deployment + Size int32 `json:"size"` +} + +// +k8s:openapi-gen=true +type MemcachedStatus struct { + // Nodes are the names of the memcached pods + Nodes []string `json:"nodes"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Memcached is the Schema for the memcacheds API +// +k8s:openapi-gen=true +type Memcached struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MemcachedSpec `json:"spec,omitempty"` + Status MemcachedStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// MemcachedList contains a list of Memcached +type MemcachedList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Memcached `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Memcached{}, &MemcachedList{}) +} diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/register.go b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/register.go new file mode 100644 index 0000000000..6eb96210b8 --- /dev/null +++ b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/register.go @@ -0,0 +1,33 @@ +// Copyright 2018 The Operator-SDK 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. + +// NOTE: Boilerplate only. Ignore this file. + +// Package v1alpha1 contains API Schema definitions for the cache v1alpha1 API group +// +k8s:deepcopy-gen=package,register +// +groupName=cache.example.com +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "cache.example.com", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} +) diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..24be9754bf --- /dev/null +++ b/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,107 @@ +// +build !ignore_autogenerated + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Memcached) DeepCopyInto(out *Memcached) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached. +func (in *Memcached) DeepCopy() *Memcached { + if in == nil { + return nil + } + out := new(Memcached) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Memcached) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedList) DeepCopyInto(out *MemcachedList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Memcached, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList. +func (in *MemcachedList) DeepCopy() *MemcachedList { + if in == nil { + return nil + } + out := new(MemcachedList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MemcachedList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. +func (in *MemcachedSpec) DeepCopy() *MemcachedSpec { + if in == nil { + return nil + } + out := new(MemcachedSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) { + *out = *in + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus. +func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { + if in == nil { + return nil + } + out := new(MemcachedStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/scaffold/testData/pkg/controller/add_memcached.go b/pkg/scaffold/testData/pkg/controller/add_memcached.go new file mode 100644 index 0000000000..cbab556a26 --- /dev/null +++ b/pkg/scaffold/testData/pkg/controller/add_memcached.go @@ -0,0 +1,24 @@ +// Copyright 2018 The Operator-SDK 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 controller + +import ( + "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/controller/memcached" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, memcached.Add) +} diff --git a/pkg/scaffold/testData/pkg/controller/controller.go b/pkg/scaffold/testData/pkg/controller/controller.go new file mode 100644 index 0000000000..c678b7f9c6 --- /dev/null +++ b/pkg/scaffold/testData/pkg/controller/controller.go @@ -0,0 +1,32 @@ +// Copyright 2018 The Operator-SDK 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 controller + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// AddToManagerFuncs is a list of functions to add all Controllers to the Manager +var AddToManagerFuncs []func(manager.Manager) error + +// AddToManager adds all Controllers to the Manager +func AddToManager(m manager.Manager) error { + for _, f := range AddToManagerFuncs { + if err := f(m); err != nil { + return err + } + } + return nil +} diff --git a/pkg/scaffold/testData/pkg/controller/memcached/memcached_controller.go b/pkg/scaffold/testData/pkg/controller/memcached/memcached_controller.go new file mode 100644 index 0000000000..e139d4d538 --- /dev/null +++ b/pkg/scaffold/testData/pkg/controller/memcached/memcached_controller.go @@ -0,0 +1,243 @@ +// Copyright 2018 The Operator-SDK 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 memcached + +import ( + "context" + "reflect" + + cachev1alpha1 "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/apis/cache/v1alpha1" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +var log = logf.Log.WithName("controller_memcached") + +// Add creates a new Memcached Controller and adds it to the Manager. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("memcached-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource Memcached + err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) + if err != nil { + return err + } + + // TODO(user): Modify this to be the types you create that are owned by the primary resource + // Watch for changes to secondary resource Pods and requeue the owner Memcached + err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &cachev1alpha1.Memcached{}, + }) + if err != nil { + return err + } + + return nil +} + +var _ reconcile.Reconciler = &ReconcileMemcached{} + +// ReconcileMemcached reconciles a Memcached object +type ReconcileMemcached struct { + // TODO: Clarify the split client + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme +} + +// Reconcile reads that state of the cluster for a Memcached object and makes changes based on the state read +// and what is in the Memcached.Spec +// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates +// a Memcached Deployment for each Memcached CR +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { + reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) + reqLogger.Info("Reconciling Memcached.") + + // Fetch the Memcached instance + memcached := &cachev1alpha1.Memcached{} + err := r.client.Get(context.TODO(), request.NamespacedName, memcached) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted.") + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + reqLogger.Error(err, "failed to get Memcached.") + return reconcile.Result{}, err + } + + // Check if the deployment already exists, if not create a new one + found := &appsv1.Deployment{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) + if err != nil && errors.IsNotFound(err) { + // Define a new deployment + dep := r.deploymentForMemcached(memcached) + reqLogger.Info("Creating a new Deployment.", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + err = r.client.Create(context.TODO(), dep) + if err != nil { + reqLogger.Error(err, "failed to create new Deployment.", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + return reconcile.Result{}, err + } + // Deployment created successfully - return and requeue + return reconcile.Result{Requeue: true}, nil + } else if err != nil { + reqLogger.Error(err, "failed to get Deployment.") + return reconcile.Result{}, err + } + + // Ensure the deployment size is the same as the spec + size := memcached.Spec.Size + if *found.Spec.Replicas != size { + found.Spec.Replicas = &size + err = r.client.Update(context.TODO(), found) + if err != nil { + reqLogger.Error(err, "failed to update Deployment.", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) + return reconcile.Result{}, err + } + // Spec updated - return and requeue + return reconcile.Result{Requeue: true}, nil + } + + // Update the Memcached status with the pod names + // List the pods for this memcached's deployment + podList := &corev1.PodList{} + labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) + listOps := &client.ListOptions{ + Namespace: memcached.Namespace, + LabelSelector: labelSelector, + // HACK: due to a fake client bug, ListOptions.Raw.TypeMeta must be + // explicitly populated for testing. + // + // See https://github.com/kubernetes-sigs/controller-runtime/issues/168 + Raw: &metav1.ListOptions{ + TypeMeta: metav1.TypeMeta{ + Kind: "Memcached", + APIVersion: cachev1alpha1.SchemeGroupVersion.Version, + }, + }, + } + err = r.client.List(context.TODO(), listOps, podList) + if err != nil { + reqLogger.Error(err, "failed to list pods.", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name) + return reconcile.Result{}, err + } + podNames := getPodNames(podList.Items) + + // Update status.Nodes if needed + if !reflect.DeepEqual(podNames, memcached.Status.Nodes) { + memcached.Status.Nodes = podNames + err := r.client.Status().Update(context.TODO(), memcached) + if err != nil { + reqLogger.Error(err, "failed to update Memcached status.") + return reconcile.Result{}, err + } + } + + return reconcile.Result{}, nil +} + +// deploymentForMemcached returns a memcached Deployment object +func (r *ReconcileMemcached) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment { + ls := labelsForMemcached(m.Name) + replicas := m.Spec.Size + + dep := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: m.Name, + Namespace: m.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: ls, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: ls, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: "memcached:1.4.36-alpine", + Name: "memcached", + Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, + Ports: []corev1.ContainerPort{{ + ContainerPort: 11211, + Name: "memcached", + }}, + }}, + }, + }, + }, + } + // Set Memcached instance as the owner and controller + controllerutil.SetControllerReference(m, dep, r.scheme) + return dep +} + +// labelsForMemcached returns the labels for selecting the resources +// belonging to the given memcached CR name. +func labelsForMemcached(name string) map[string]string { + return map[string]string{"app": "memcached", "memcached_cr": name} +} + +// getPodNames returns the pod names of the array of pods passed in +func getPodNames(pods []corev1.Pod) []string { + var podNames []string + for _, pod := range pods { + podNames = append(podNames, pod.Name) + } + return podNames +} From 590d95e54a0375e7fc6e5606735ca51cc03c6cd3 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 15:20:33 -0800 Subject: [PATCH 11/34] rename test dir to avoid import cycles during e2e test --- pkg/scaffold/_testdata/cmd/manager/main.go | 0 .../crds/cache_v1alpha1_memcached_cr.yaml | 0 .../crds/cache_v1alpha1_memcached_crd.yaml | 0 .../deploy/namespace-init.yaml | 0 .../deploy/operator.yaml | 0 .../{testData => _testdata}/deploy/role.yaml | 0 .../deploy/role_binding.yaml | 0 .../deploy/service_account.yaml | 0 .../deploy/test-pod.yaml | 0 .../pkg/apis/addtoscheme_cache_v1alpha1.go | 0 .../{testData => _testdata}/pkg/apis/apis.go | 0 .../pkg/apis/cache/v1alpha1/doc.go | 0 .../apis/cache/v1alpha1/memcached_types.go | 0 .../pkg/apis/cache/v1alpha1/register.go | 0 .../cache/v1alpha1/zz_generated.deepcopy.go | 0 .../pkg/controller/add_memcached.go | 0 .../pkg/controller/controller.go | 0 .../memcached/memcached_controller.go | 0 pkg/scaffold/crd_test.go | 4 +- pkg/scaffold/testData/cmd/manager/main.go | 109 ------------------ 20 files changed, 2 insertions(+), 111 deletions(-) create mode 100644 pkg/scaffold/_testdata/cmd/manager/main.go rename pkg/scaffold/{testData => _testdata}/deploy/crds/cache_v1alpha1_memcached_cr.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/crds/cache_v1alpha1_memcached_crd.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/namespace-init.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/operator.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/role.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/role_binding.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/service_account.yaml (100%) rename pkg/scaffold/{testData => _testdata}/deploy/test-pod.yaml (100%) rename pkg/scaffold/{testData => _testdata}/pkg/apis/addtoscheme_cache_v1alpha1.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/apis/apis.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/apis/cache/v1alpha1/doc.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/apis/cache/v1alpha1/memcached_types.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/apis/cache/v1alpha1/register.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/controller/add_memcached.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/controller/controller.go (100%) rename pkg/scaffold/{testData => _testdata}/pkg/controller/memcached/memcached_controller.go (100%) delete mode 100644 pkg/scaffold/testData/cmd/manager/main.go diff --git a/pkg/scaffold/_testdata/cmd/manager/main.go b/pkg/scaffold/_testdata/cmd/manager/main.go new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_cr.yaml b/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_cr.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_cr.yaml rename to pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_cr.yaml diff --git a/pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_crd.yaml b/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_crd.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/crds/cache_v1alpha1_memcached_crd.yaml rename to pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_crd.yaml diff --git a/pkg/scaffold/testData/deploy/namespace-init.yaml b/pkg/scaffold/_testdata/deploy/namespace-init.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/namespace-init.yaml rename to pkg/scaffold/_testdata/deploy/namespace-init.yaml diff --git a/pkg/scaffold/testData/deploy/operator.yaml b/pkg/scaffold/_testdata/deploy/operator.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/operator.yaml rename to pkg/scaffold/_testdata/deploy/operator.yaml diff --git a/pkg/scaffold/testData/deploy/role.yaml b/pkg/scaffold/_testdata/deploy/role.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/role.yaml rename to pkg/scaffold/_testdata/deploy/role.yaml diff --git a/pkg/scaffold/testData/deploy/role_binding.yaml b/pkg/scaffold/_testdata/deploy/role_binding.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/role_binding.yaml rename to pkg/scaffold/_testdata/deploy/role_binding.yaml diff --git a/pkg/scaffold/testData/deploy/service_account.yaml b/pkg/scaffold/_testdata/deploy/service_account.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/service_account.yaml rename to pkg/scaffold/_testdata/deploy/service_account.yaml diff --git a/pkg/scaffold/testData/deploy/test-pod.yaml b/pkg/scaffold/_testdata/deploy/test-pod.yaml similarity index 100% rename from pkg/scaffold/testData/deploy/test-pod.yaml rename to pkg/scaffold/_testdata/deploy/test-pod.yaml diff --git a/pkg/scaffold/testData/pkg/apis/addtoscheme_cache_v1alpha1.go b/pkg/scaffold/_testdata/pkg/apis/addtoscheme_cache_v1alpha1.go similarity index 100% rename from pkg/scaffold/testData/pkg/apis/addtoscheme_cache_v1alpha1.go rename to pkg/scaffold/_testdata/pkg/apis/addtoscheme_cache_v1alpha1.go diff --git a/pkg/scaffold/testData/pkg/apis/apis.go b/pkg/scaffold/_testdata/pkg/apis/apis.go similarity index 100% rename from pkg/scaffold/testData/pkg/apis/apis.go rename to pkg/scaffold/_testdata/pkg/apis/apis.go diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/doc.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/doc.go similarity index 100% rename from pkg/scaffold/testData/pkg/apis/cache/v1alpha1/doc.go rename to pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/doc.go diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/memcached_types.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/memcached_types.go similarity index 100% rename from pkg/scaffold/testData/pkg/apis/cache/v1alpha1/memcached_types.go rename to pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/memcached_types.go diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/register.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/register.go similarity index 100% rename from pkg/scaffold/testData/pkg/apis/cache/v1alpha1/register.go rename to pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/register.go diff --git a/pkg/scaffold/testData/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go similarity index 100% rename from pkg/scaffold/testData/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go rename to pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go diff --git a/pkg/scaffold/testData/pkg/controller/add_memcached.go b/pkg/scaffold/_testdata/pkg/controller/add_memcached.go similarity index 100% rename from pkg/scaffold/testData/pkg/controller/add_memcached.go rename to pkg/scaffold/_testdata/pkg/controller/add_memcached.go diff --git a/pkg/scaffold/testData/pkg/controller/controller.go b/pkg/scaffold/_testdata/pkg/controller/controller.go similarity index 100% rename from pkg/scaffold/testData/pkg/controller/controller.go rename to pkg/scaffold/_testdata/pkg/controller/controller.go diff --git a/pkg/scaffold/testData/pkg/controller/memcached/memcached_controller.go b/pkg/scaffold/_testdata/pkg/controller/memcached/memcached_controller.go similarity index 100% rename from pkg/scaffold/testData/pkg/controller/memcached/memcached_controller.go rename to pkg/scaffold/_testdata/pkg/controller/memcached/memcached_controller.go diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index 1da2464d31..9d7803a7b9 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -34,9 +34,9 @@ func TestCRDGoProject(t *testing.T) { if err != nil { t.Fatal(err) } - // Set the project and repo paths to {abs}/testData, which contains pkg/apis + // Set the project and repo paths to {abs}/_testdata, which contains pkg/apis // for the memcached-operator. - td := "testData" + td := "_testdata" repo := absPath[strings.Index(absPath, "github.com"):] cfg := &input.Config{ Repo: filepath.Join(repo, td), diff --git a/pkg/scaffold/testData/cmd/manager/main.go b/pkg/scaffold/testData/cmd/manager/main.go deleted file mode 100644 index 4394fea566..0000000000 --- a/pkg/scaffold/testData/cmd/manager/main.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 main - -import ( - "context" - "flag" - "fmt" - "os" - "runtime" - - "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/apis" - "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/controller" - - "github.com/operator-framework/operator-sdk/pkg/k8sutil" - "github.com/operator-framework/operator-sdk/pkg/leader" - "github.com/operator-framework/operator-sdk/pkg/ready" - sdkVersion "github.com/operator-framework/operator-sdk/version" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/controller-runtime/pkg/manager" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" - "sigs.k8s.io/controller-runtime/pkg/runtime/signals" -) - -var log = logf.Log.WithName("cmd") - -func printVersion() { - log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) - log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) - log.Info(fmt.Sprintf("operator-sdk Version: %v", sdkVersion.Version)) -} - -func main() { - flag.Parse() - - // The logger instantiated here can be changed to any logger - // implementing the logr.Logger interface. This logger will - // be propagated through the whole operator, generating - // uniform and structured logs. - logf.SetLogger(logf.ZapLogger(false)) - - printVersion() - - namespace, err := k8sutil.GetWatchNamespace() - if err != nil { - log.Error(err, "failed to get watch namespace") - os.Exit(1) - } - - // Get a config to talk to the apiserver - cfg, err := config.GetConfig() - if err != nil { - log.Error(err, "") - os.Exit(1) - } - - // Become the leader before proceeding - leader.Become(context.TODO(), "memcached-operator-lock") - - r := ready.NewFileReady() - err = r.Set() - if err != nil { - log.Error(err, "") - os.Exit(1) - } - defer r.Unset() - - // Create a new Cmd to provide shared dependencies and start components - mgr, err := manager.New(cfg, manager.Options{Namespace: namespace}) - if err != nil { - log.Error(err, "") - os.Exit(1) - } - - log.Info("Registering Components.") - - // Setup Scheme for all resources - if err := apis.AddToScheme(mgr.GetScheme()); err != nil { - log.Error(err, "") - os.Exit(1) - } - - // Setup all Controllers - if err := controller.AddToManager(mgr); err != nil { - log.Error(err, "") - os.Exit(1) - } - - log.Info("Starting the Cmd.") - - // Start the Cmd - if err := mgr.Start(signals.SetupSignalHandler()); err != nil { - log.Error(err, "manager exited non-zero") - os.Exit(1) - } -} From 05c73184176c4007cd2dac091aaf95098ef2cf90 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 15:32:24 -0800 Subject: [PATCH 12/34] pkg/scaffold/crd.go: add IsOperatorGo field for controller-tools crd generator condition --- commands/operator-sdk/cmd/add/api.go | 2 +- commands/operator-sdk/cmd/add/crd.go | 2 +- commands/operator-sdk/cmd/generate/openapi.go | 5 ++++- pkg/scaffold/crd.go | 6 ++++-- pkg/scaffold/crd_test.go | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/commands/operator-sdk/cmd/add/api.go b/commands/operator-sdk/cmd/add/api.go index a90c4080d8..7199679676 100644 --- a/commands/operator-sdk/cmd/add/api.go +++ b/commands/operator-sdk/cmd/add/api.go @@ -89,7 +89,7 @@ func apiRun(cmd *cobra.Command, args []string) { &scaffold.Register{Resource: r}, &scaffold.Doc{Resource: r}, &scaffold.Cr{Resource: r}, - &scaffold.Crd{Resource: r}, + &scaffold.Crd{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, ) if err != nil { log.Fatalf("add scaffold failed: (%v)", err) diff --git a/commands/operator-sdk/cmd/add/crd.go b/commands/operator-sdk/cmd/add/crd.go index 617d6612ca..a5392909c5 100644 --- a/commands/operator-sdk/cmd/add/crd.go +++ b/commands/operator-sdk/cmd/add/crd.go @@ -69,7 +69,7 @@ func crdFunc(cmd *cobra.Command, args []string) { s := scaffold.Scaffold{} err = s.Execute(cfg, - &scaffold.Crd{Resource: resource}, + &scaffold.Crd{Resource: resource, IsOperatorGo: projutil.IsOperatorGo()}, &scaffold.Cr{Resource: resource}, ) if err != nil { diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 6f439603dc..712b6fdb4e 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -98,7 +98,10 @@ func OpenAPIGen() { if err != nil { log.Fatal(err) } - if err = s.Execute(cfg, &scaffold.Crd{Resource: r}); err != nil { + err = s.Execute(cfg, + &scaffold.Crd{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, + ) + if err != nil { log.Fatal(err) } } diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index d95823c817..5c5cfb759d 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -22,7 +22,6 @@ import ( "strings" "sync" - "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/pkg/scaffold/input" "github.com/ghodss/yaml" @@ -38,6 +37,9 @@ type Crd struct { // Resource defines the inputs for the new custom resource definition Resource *Resource + + // IsOperatorGo is true when the operator is written in Go. + IsOperatorGo bool } func (s *Crd) GetInput() (input.Input, error) { @@ -81,7 +83,7 @@ func (s *Crd) CustomRender() ([]byte, error) { // controller-tools' generators read and make crds for all apis in pkg/apis, // so generate crds in a cached, in-memory fs to extract the data we need. - if !cache.fileExists(path) && projutil.IsOperatorGo() { + if !cache.fileExists(path) && s.IsOperatorGo { g := &crdgenerator.Generator{ RootPath: s.AbsProjectPath, Domain: strings.SplitN(s.Resource.FullGroup, ".", 2)[1], diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index 9d7803a7b9..a2e47402cf 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -47,7 +47,7 @@ func TestCRDGoProject(t *testing.T) { t.Fatal(err) } defer func() { os.Chdir(absPath) }() - err = s.Execute(cfg, &Crd{Resource: r}) + err = s.Execute(cfg, &Crd{Resource: r, IsOperatorGo: true}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) } From 397a9fe23c16348b4cc80ce66c57468180ce93e5 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 15:59:29 -0800 Subject: [PATCH 13/34] use test/test-framework code as test project --- pkg/scaffold/_testdata/cmd/manager/main.go | 0 .../crds/cache_v1alpha1_memcached_cr.yaml | 7 - .../crds/cache_v1alpha1_memcached_crd.yaml | 13 - .../_testdata/deploy/namespace-init.yaml | 83 ------ pkg/scaffold/_testdata/deploy/operator.yaml | 32 --- pkg/scaffold/_testdata/deploy/role.yaml | 33 --- .../_testdata/deploy/role_binding.yaml | 11 - .../_testdata/deploy/service_account.yaml | 4 - pkg/scaffold/_testdata/deploy/test-pod.yaml | 16 -- .../pkg/apis/addtoscheme_cache_v1alpha1.go | 24 -- pkg/scaffold/_testdata/pkg/apis/apis.go | 27 -- .../_testdata/pkg/apis/cache/v1alpha1/doc.go | 18 -- .../apis/cache/v1alpha1/memcached_types.go | 59 ----- .../pkg/apis/cache/v1alpha1/register.go | 33 --- .../cache/v1alpha1/zz_generated.deepcopy.go | 107 -------- .../_testdata/pkg/controller/add_memcached.go | 24 -- .../_testdata/pkg/controller/controller.go | 32 --- .../memcached/memcached_controller.go | 243 ------------------ pkg/scaffold/crd_test.go | 14 +- 19 files changed, 7 insertions(+), 773 deletions(-) delete mode 100644 pkg/scaffold/_testdata/cmd/manager/main.go delete mode 100644 pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_cr.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_crd.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/namespace-init.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/operator.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/role.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/role_binding.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/service_account.yaml delete mode 100644 pkg/scaffold/_testdata/deploy/test-pod.yaml delete mode 100644 pkg/scaffold/_testdata/pkg/apis/addtoscheme_cache_v1alpha1.go delete mode 100644 pkg/scaffold/_testdata/pkg/apis/apis.go delete mode 100644 pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/doc.go delete mode 100644 pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/memcached_types.go delete mode 100644 pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/register.go delete mode 100644 pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go delete mode 100644 pkg/scaffold/_testdata/pkg/controller/add_memcached.go delete mode 100644 pkg/scaffold/_testdata/pkg/controller/controller.go delete mode 100644 pkg/scaffold/_testdata/pkg/controller/memcached/memcached_controller.go diff --git a/pkg/scaffold/_testdata/cmd/manager/main.go b/pkg/scaffold/_testdata/cmd/manager/main.go deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_cr.yaml b/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_cr.yaml deleted file mode 100644 index 2b8f17c399..0000000000 --- a/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_cr.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: cache.example.com/v1alpha1 -kind: Memcached -metadata: - name: example-memcached -spec: - # Add fields here - size: 3 diff --git a/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_crd.yaml b/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_crd.yaml deleted file mode 100644 index ab3f99f18d..0000000000 --- a/pkg/scaffold/_testdata/deploy/crds/cache_v1alpha1_memcached_crd.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: memcacheds.cache.example.com -spec: - group: cache.example.com - names: - kind: Memcached - listKind: MemcachedList - plural: memcacheds - singular: memcached - scope: Namespaced - version: v1alpha1 diff --git a/pkg/scaffold/_testdata/deploy/namespace-init.yaml b/pkg/scaffold/_testdata/deploy/namespace-init.yaml deleted file mode 100644 index 73b8147671..0000000000 --- a/pkg/scaffold/_testdata/deploy/namespace-init.yaml +++ /dev/null @@ -1,83 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: memcached-operator ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: null - name: memcached-operator -rules: -- apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - verbs: - - '*' -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' -- apiGroups: - - cache.example.com - resources: - - '*' - verbs: - - '*' ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: memcached-operator -subjects: -- kind: ServiceAccount - name: memcached-operator -roleRef: - kind: Role - name: memcached-operator - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: memcached-operator -spec: - replicas: 1 - selector: - matchLabels: - name: memcached-operator - template: - metadata: - labels: - name: memcached-operator - spec: - serviceAccountName: memcached-operator - containers: - - name: memcached-operator - # Replace this with the built image name - image: quay.io/coreos/operator-sdk-dev:test-framework-operator-runtime - ports: - - containerPort: 60000 - name: metrics - command: - - memcached-operator - imagePullPolicy: Always - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: OPERATOR_NAME - value: "memcached-operator" diff --git a/pkg/scaffold/_testdata/deploy/operator.yaml b/pkg/scaffold/_testdata/deploy/operator.yaml deleted file mode 100644 index 6552adc6e3..0000000000 --- a/pkg/scaffold/_testdata/deploy/operator.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: memcached-operator -spec: - replicas: 1 - selector: - matchLabels: - name: memcached-operator - template: - metadata: - labels: - name: memcached-operator - spec: - serviceAccountName: memcached-operator - containers: - - name: memcached-operator - # Replace this with the built image name - image: quay.io/coreos/operator-sdk-dev:test-framework-operator-runtime - ports: - - containerPort: 60000 - name: metrics - command: - - memcached-operator - imagePullPolicy: Always - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: OPERATOR_NAME - value: "memcached-operator" diff --git a/pkg/scaffold/_testdata/deploy/role.yaml b/pkg/scaffold/_testdata/deploy/role.yaml deleted file mode 100644 index a3075d534d..0000000000 --- a/pkg/scaffold/_testdata/deploy/role.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: null - name: memcached-operator -rules: -- apiGroups: - - "" - resources: - - pods - - services - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - verbs: - - '*' -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' -- apiGroups: - - cache.example.com - resources: - - '*' - verbs: - - '*' diff --git a/pkg/scaffold/_testdata/deploy/role_binding.yaml b/pkg/scaffold/_testdata/deploy/role_binding.yaml deleted file mode 100644 index 322ecc9e6a..0000000000 --- a/pkg/scaffold/_testdata/deploy/role_binding.yaml +++ /dev/null @@ -1,11 +0,0 @@ -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: memcached-operator -subjects: -- kind: ServiceAccount - name: memcached-operator -roleRef: - kind: Role - name: memcached-operator - apiGroup: rbac.authorization.k8s.io diff --git a/pkg/scaffold/_testdata/deploy/service_account.yaml b/pkg/scaffold/_testdata/deploy/service_account.yaml deleted file mode 100644 index 8d58bc7832..0000000000 --- a/pkg/scaffold/_testdata/deploy/service_account.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: memcached-operator diff --git a/pkg/scaffold/_testdata/deploy/test-pod.yaml b/pkg/scaffold/_testdata/deploy/test-pod.yaml deleted file mode 100644 index bb7e325158..0000000000 --- a/pkg/scaffold/_testdata/deploy/test-pod.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: -test -spec: - restartPolicy: Never - containers: - - name: -test - image: - imagePullPolicy: Always - command: ["/go-test.sh"] - env: - - name: - valueFrom: - fieldRef: - fieldPath: metadata.namespace diff --git a/pkg/scaffold/_testdata/pkg/apis/addtoscheme_cache_v1alpha1.go b/pkg/scaffold/_testdata/pkg/apis/addtoscheme_cache_v1alpha1.go deleted file mode 100644 index ca10efeee1..0000000000 --- a/pkg/scaffold/_testdata/pkg/apis/addtoscheme_cache_v1alpha1.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 apis - -import ( - "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/apis/cache/v1alpha1" -) - -func init() { - // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back - AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) -} diff --git a/pkg/scaffold/_testdata/pkg/apis/apis.go b/pkg/scaffold/_testdata/pkg/apis/apis.go deleted file mode 100644 index 6f79149168..0000000000 --- a/pkg/scaffold/_testdata/pkg/apis/apis.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 apis - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -// AddToSchemes may be used to add all resources defined in the project to a Scheme -var AddToSchemes runtime.SchemeBuilder - -// AddToScheme adds all Resources to the Scheme -func AddToScheme(s *runtime.Scheme) error { - return AddToSchemes.AddToScheme(s) -} diff --git a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/doc.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/doc.go deleted file mode 100644 index 84fd6d207f..0000000000 --- a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 v1alpha1 contains API Schema definitions for the cache v1alpha1 API group -// +k8s:deepcopy-gen=package,register -// +groupName=cache.example.com -package v1alpha1 diff --git a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/memcached_types.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/memcached_types.go deleted file mode 100644 index f66f9dc6d4..0000000000 --- a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/memcached_types.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - -// +k8s:openapi-gen=true -type MemcachedSpec struct { - // Size is the size of the memcached deployment - Size int32 `json:"size"` -} - -// +k8s:openapi-gen=true -type MemcachedStatus struct { - // Nodes are the names of the memcached pods - Nodes []string `json:"nodes"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Memcached is the Schema for the memcacheds API -// +k8s:openapi-gen=true -type Memcached struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec MemcachedSpec `json:"spec,omitempty"` - Status MemcachedStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// MemcachedList contains a list of Memcached -type MemcachedList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Memcached `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Memcached{}, &MemcachedList{}) -} diff --git a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/register.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/register.go deleted file mode 100644 index 6eb96210b8..0000000000 --- a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/register.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018 The Operator-SDK 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. - -// NOTE: Boilerplate only. Ignore this file. - -// Package v1alpha1 contains API Schema definitions for the cache v1alpha1 API group -// +k8s:deepcopy-gen=package,register -// +groupName=cache.example.com -package v1alpha1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" -) - -var ( - // SchemeGroupVersion is group version used to register these objects - SchemeGroupVersion = schema.GroupVersion{Group: "cache.example.com", Version: "v1alpha1"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} -) diff --git a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go b/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 24be9754bf..0000000000 --- a/pkg/scaffold/_testdata/pkg/apis/cache/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,107 +0,0 @@ -// +build !ignore_autogenerated - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Memcached) DeepCopyInto(out *Memcached) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached. -func (in *Memcached) DeepCopy() *Memcached { - if in == nil { - return nil - } - out := new(Memcached) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Memcached) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MemcachedList) DeepCopyInto(out *MemcachedList) { - *out = *in - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Memcached, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList. -func (in *MemcachedList) DeepCopy() *MemcachedList { - if in == nil { - return nil - } - out := new(MemcachedList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *MemcachedList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. -func (in *MemcachedSpec) DeepCopy() *MemcachedSpec { - if in == nil { - return nil - } - out := new(MemcachedSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) { - *out = *in - if in.Nodes != nil { - in, out := &in.Nodes, &out.Nodes - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus. -func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { - if in == nil { - return nil - } - out := new(MemcachedStatus) - in.DeepCopyInto(out) - return out -} diff --git a/pkg/scaffold/_testdata/pkg/controller/add_memcached.go b/pkg/scaffold/_testdata/pkg/controller/add_memcached.go deleted file mode 100644 index cbab556a26..0000000000 --- a/pkg/scaffold/_testdata/pkg/controller/add_memcached.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 controller - -import ( - "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/controller/memcached" -) - -func init() { - // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. - AddToManagerFuncs = append(AddToManagerFuncs, memcached.Add) -} diff --git a/pkg/scaffold/_testdata/pkg/controller/controller.go b/pkg/scaffold/_testdata/pkg/controller/controller.go deleted file mode 100644 index c678b7f9c6..0000000000 --- a/pkg/scaffold/_testdata/pkg/controller/controller.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 controller - -import ( - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -// AddToManagerFuncs is a list of functions to add all Controllers to the Manager -var AddToManagerFuncs []func(manager.Manager) error - -// AddToManager adds all Controllers to the Manager -func AddToManager(m manager.Manager) error { - for _, f := range AddToManagerFuncs { - if err := f(m); err != nil { - return err - } - } - return nil -} diff --git a/pkg/scaffold/_testdata/pkg/controller/memcached/memcached_controller.go b/pkg/scaffold/_testdata/pkg/controller/memcached/memcached_controller.go deleted file mode 100644 index e139d4d538..0000000000 --- a/pkg/scaffold/_testdata/pkg/controller/memcached/memcached_controller.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2018 The Operator-SDK 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 memcached - -import ( - "context" - "reflect" - - cachev1alpha1 "github.com/operator-framework/operator-sdk/pkg/scaffold/testData/pkg/apis/cache/v1alpha1" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" - "sigs.k8s.io/controller-runtime/pkg/source" -) - -var log = logf.Log.WithName("controller_memcached") - -// Add creates a new Memcached Controller and adds it to the Manager. The Manager will set fields on the Controller -// and Start it when the Manager is Started. -func Add(mgr manager.Manager) error { - return add(mgr, newReconciler(mgr)) -} - -// newReconciler returns a new reconcile.Reconciler -func newReconciler(mgr manager.Manager) reconcile.Reconciler { - return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme()} -} - -// add adds a new Controller to mgr with r as the reconcile.Reconciler -func add(mgr manager.Manager, r reconcile.Reconciler) error { - // Create a new controller - c, err := controller.New("memcached-controller", mgr, controller.Options{Reconciler: r}) - if err != nil { - return err - } - - // Watch for changes to primary resource Memcached - err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) - if err != nil { - return err - } - - // TODO(user): Modify this to be the types you create that are owned by the primary resource - // Watch for changes to secondary resource Pods and requeue the owner Memcached - err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ - IsController: true, - OwnerType: &cachev1alpha1.Memcached{}, - }) - if err != nil { - return err - } - - return nil -} - -var _ reconcile.Reconciler = &ReconcileMemcached{} - -// ReconcileMemcached reconciles a Memcached object -type ReconcileMemcached struct { - // TODO: Clarify the split client - // This client, initialized using mgr.Client() above, is a split client - // that reads objects from the cache and writes to the apiserver - client client.Client - scheme *runtime.Scheme -} - -// Reconcile reads that state of the cluster for a Memcached object and makes changes based on the state read -// and what is in the Memcached.Spec -// TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates -// a Memcached Deployment for each Memcached CR -// Note: -// The Controller will requeue the Request to be processed again if the returned error is non-nil or -// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. -func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { - reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - reqLogger.Info("Reconciling Memcached.") - - // Fetch the Memcached instance - memcached := &cachev1alpha1.Memcached{} - err := r.client.Get(context.TODO(), request.NamespacedName, memcached) - if err != nil { - if errors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - reqLogger.Info("Memcached resource not found. Ignoring since object must be deleted.") - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - reqLogger.Error(err, "failed to get Memcached.") - return reconcile.Result{}, err - } - - // Check if the deployment already exists, if not create a new one - found := &appsv1.Deployment{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) - if err != nil && errors.IsNotFound(err) { - // Define a new deployment - dep := r.deploymentForMemcached(memcached) - reqLogger.Info("Creating a new Deployment.", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) - err = r.client.Create(context.TODO(), dep) - if err != nil { - reqLogger.Error(err, "failed to create new Deployment.", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) - return reconcile.Result{}, err - } - // Deployment created successfully - return and requeue - return reconcile.Result{Requeue: true}, nil - } else if err != nil { - reqLogger.Error(err, "failed to get Deployment.") - return reconcile.Result{}, err - } - - // Ensure the deployment size is the same as the spec - size := memcached.Spec.Size - if *found.Spec.Replicas != size { - found.Spec.Replicas = &size - err = r.client.Update(context.TODO(), found) - if err != nil { - reqLogger.Error(err, "failed to update Deployment.", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - return reconcile.Result{}, err - } - // Spec updated - return and requeue - return reconcile.Result{Requeue: true}, nil - } - - // Update the Memcached status with the pod names - // List the pods for this memcached's deployment - podList := &corev1.PodList{} - labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) - listOps := &client.ListOptions{ - Namespace: memcached.Namespace, - LabelSelector: labelSelector, - // HACK: due to a fake client bug, ListOptions.Raw.TypeMeta must be - // explicitly populated for testing. - // - // See https://github.com/kubernetes-sigs/controller-runtime/issues/168 - Raw: &metav1.ListOptions{ - TypeMeta: metav1.TypeMeta{ - Kind: "Memcached", - APIVersion: cachev1alpha1.SchemeGroupVersion.Version, - }, - }, - } - err = r.client.List(context.TODO(), listOps, podList) - if err != nil { - reqLogger.Error(err, "failed to list pods.", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name) - return reconcile.Result{}, err - } - podNames := getPodNames(podList.Items) - - // Update status.Nodes if needed - if !reflect.DeepEqual(podNames, memcached.Status.Nodes) { - memcached.Status.Nodes = podNames - err := r.client.Status().Update(context.TODO(), memcached) - if err != nil { - reqLogger.Error(err, "failed to update Memcached status.") - return reconcile.Result{}, err - } - } - - return reconcile.Result{}, nil -} - -// deploymentForMemcached returns a memcached Deployment object -func (r *ReconcileMemcached) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment { - ls := labelsForMemcached(m.Name) - replicas := m.Spec.Size - - dep := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: m.Name, - Namespace: m.Namespace, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: ls, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: ls, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Image: "memcached:1.4.36-alpine", - Name: "memcached", - Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, - Ports: []corev1.ContainerPort{{ - ContainerPort: 11211, - Name: "memcached", - }}, - }}, - }, - }, - }, - } - // Set Memcached instance as the owner and controller - controllerutil.SetControllerReference(m, dep, r.scheme) - return dep -} - -// labelsForMemcached returns the labels for selecting the resources -// belonging to the given memcached CR name. -func labelsForMemcached(name string) map[string]string { - return map[string]string{"app": "memcached", "memcached_cr": name} -} - -// getPodNames returns the pod names of the array of pods passed in -func getPodNames(pods []corev1.Pod) []string { - var podNames []string - for _, pod := range pods { - podNames = append(podNames, pod.Name) - } - return podNames -} diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index a2e47402cf..bababef175 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -34,14 +34,14 @@ func TestCRDGoProject(t *testing.T) { if err != nil { t.Fatal(err) } - // Set the project and repo paths to {abs}/_testdata, which contains pkg/apis - // for the memcached-operator. - td := "_testdata" - repo := absPath[strings.Index(absPath, "github.com"):] + // Set the project and repo paths to {abs}/test/test-framework, which + // contains pkg/apis for the memcached-operator. + tfDir := filepath.Join("test", "test-framework") + pkgIdx := strings.Index(absPath, "pkg") cfg := &input.Config{ - Repo: filepath.Join(repo, td), - AbsProjectPath: filepath.Join(absPath, td), - ProjectName: td, + Repo: filepath.Join(absPath[strings.Index(absPath, "github.com"):pkgIdx], tfDir), + AbsProjectPath: filepath.Join(absPath[:pkgIdx], tfDir), + ProjectName: tfDir, } if err := os.Chdir(cfg.AbsProjectPath); err != nil { t.Fatal(err) From ccce6733267754b377e9134d78dcfceb87e0e062 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 16:16:00 -0800 Subject: [PATCH 14/34] revert removing scaffold constants from internal/util --- internal/util/projutil/project_util.go | 9 +++++---- internal/util/yamlutil/manifest.go | 18 ++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index a2faef27c0..e9cb5ed402 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -21,6 +21,9 @@ import ( "path/filepath" "strings" + "github.com/operator-framework/operator-sdk/pkg/scaffold/ansible" + "github.com/operator-framework/operator-sdk/pkg/scaffold/helm" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -28,8 +31,6 @@ import ( const ( srcDir = "src" mainFile = "./cmd/manager/main.go" - rolesDir = "./roles" - helmChartsDir = "./helm-charts" buildDockerfile = "./build/Dockerfile" ) @@ -101,10 +102,10 @@ func GetOperatorType() OperatorType { if _, err := os.Stat(mainFile); err == nil { return OperatorTypeGo } - if stat, err := os.Stat(rolesDir); err == nil && stat.IsDir() { + if stat, err := os.Stat(ansible.RolesDir); err == nil && stat.IsDir() { return OperatorTypeAnsible } - if stat, err := os.Stat(helmChartsDir); err == nil && stat.IsDir() { + if stat, err := os.Stat(helm.HelmChartsDir); err == nil && stat.IsDir() { return OperatorTypeHelm } return OperatorTypeUnknown diff --git a/internal/util/yamlutil/manifest.go b/internal/util/yamlutil/manifest.go index a9ce9601f4..5b0df75203 100644 --- a/internal/util/yamlutil/manifest.go +++ b/internal/util/yamlutil/manifest.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/operator-framework/operator-sdk/internal/util/fileutil" + "github.com/operator-framework/operator-sdk/pkg/scaffold" log "github.com/sirupsen/logrus" ) @@ -50,8 +51,6 @@ func CombineManifests(base []byte, manifests ...[]byte) []byte { return base } -const deployDir = "deploy" - // GenerateCombinedNamespacedManifest creates a temporary manifest yaml // containing all standard namespaced resource manifests combined into 1 file func GenerateCombinedNamespacedManifest() (*os.File, error) { @@ -61,19 +60,19 @@ func GenerateCombinedNamespacedManifest() (*os.File, error) { } defer file.Close() - sa, err := ioutil.ReadFile(filepath.Join(deployDir, "service_account.yaml")) + sa, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.ServiceAccountYamlFile)) if err != nil { log.Warnf("could not find the serviceaccount manifest: (%v)", err) } - role, err := ioutil.ReadFile(filepath.Join(deployDir, "role.yaml")) + role, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.RoleYamlFile)) if err != nil { log.Warnf("could not find role manifest: (%v)", err) } - roleBinding, err := ioutil.ReadFile(filepath.Join(deployDir, "role_binding.yaml")) + roleBinding, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.RoleBindingYamlFile)) if err != nil { log.Warnf("could not find role_binding manifest: (%v)", err) } - operator, err := ioutil.ReadFile(filepath.Join(deployDir, "operator.yaml")) + operator, err := ioutil.ReadFile(filepath.Join(scaffold.DeployDir, scaffold.OperatorYamlFile)) if err != nil { return nil, fmt.Errorf("could not find operator manifest: (%v)", err) } @@ -101,17 +100,16 @@ func GenerateCombinedGlobalManifest() (*os.File, error) { } defer file.Close() - crdsDir := filepath.Join(deployDir, "crds") - files, err := ioutil.ReadDir(crdsDir) + files, err := ioutil.ReadDir(scaffold.CrdsDir) if err != nil { return nil, fmt.Errorf("could not read deploy directory: (%v)", err) } combined := []byte{} for _, file := range files { if strings.HasSuffix(file.Name(), "crd.yaml") { - fileBytes, err := ioutil.ReadFile(filepath.Join(crdsDir, file.Name())) + fileBytes, err := ioutil.ReadFile(filepath.Join(scaffold.CrdsDir, file.Name())) if err != nil { - return nil, fmt.Errorf("could not read file %s: (%v)", filepath.Join(crdsDir, file.Name()), err) + return nil, fmt.Errorf("could not read file %s: (%v)", filepath.Join(scaffold.CrdsDir, file.Name()), err) } combined = CombineManifests(combined, fileBytes) } From b06c74235ea7504869a2987729fadf9de3ec74af Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 16:36:27 -0800 Subject: [PATCH 15/34] revert SrcDir change and check IsOperatorGo first --- internal/util/projutil/project_util.go | 4 ++-- pkg/scaffold/crd.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index e9cb5ed402..70bcc8941b 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -29,7 +29,7 @@ import ( ) const ( - srcDir = "src" + SrcDir = "src" mainFile = "./cmd/manager/main.go" buildDockerfile = "./build/Dockerfile" ) @@ -87,7 +87,7 @@ func MustGetwd() string { // e.g: "github.com/example-inc/app-operator" func CheckAndGetProjectGoPkg() string { gopath := SetGopath(GetGopath()) - goSrc := filepath.Join(gopath, srcDir) + goSrc := filepath.Join(gopath, SrcDir) wd := MustGetwd() currPkg := strings.Replace(wd, goSrc+string(filepath.Separator), "", 1) // strip any "/" prefix from the repo path. diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index 5c5cfb759d..280a197a10 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -83,7 +83,7 @@ func (s *Crd) CustomRender() ([]byte, error) { // controller-tools' generators read and make crds for all apis in pkg/apis, // so generate crds in a cached, in-memory fs to extract the data we need. - if !cache.fileExists(path) && s.IsOperatorGo { + if s.IsOperatorGo && !cache.fileExists(path) { g := &crdgenerator.Generator{ RootPath: s.AbsProjectPath, Domain: strings.SplitN(s.Resource.FullGroup, ".", 2)[1], From 3db3e2f077bad538b6286d47f3bd480587da8787 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 20 Dec 2018 17:21:59 -0800 Subject: [PATCH 16/34] change function name --- pkg/scaffold/crd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index 280a197a10..5b38fa1db9 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -137,7 +137,7 @@ func (s *Crd) CustomRender() ([]byte, error) { // Remove controller-tools default label. delete(dstCrd.Labels, "controller-tools.k8s.io") } - addCrdStatus(dstCrd) + addCrdSubresource(dstCrd) return getCrdBytes(dstCrd) } @@ -171,7 +171,7 @@ func getCrdNamesForResource(r *Resource) apiextv1beta1.CustomResourceDefinitionN } } -func addCrdStatus(crd *apiextv1beta1.CustomResourceDefinition) { +func addCrdSubresource(crd *apiextv1beta1.CustomResourceDefinition) { if crd.Spec.Subresources == nil { crd.Spec.Subresources = &apiextv1beta1.CustomResourceSubresources{} } From 94021e58d8ef79b14e935c3b0b4715610c71b37c Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 3 Jan 2019 13:27:49 -0800 Subject: [PATCH 17/34] pkg/scaffold/crd.go: simplify unmarshalling crd --- pkg/scaffold/crd.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index 5b38fa1db9..c0a21ea6ea 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/afero" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" crdgenerator "sigs.k8s.io/controller-tools/pkg/crd/generator" ) @@ -181,14 +182,10 @@ func addCrdSubresource(crd *apiextv1beta1.CustomResourceDefinition) { } func getCrdBytes(crd *apiextv1beta1.CustomResourceDefinition) ([]byte, error) { - b, err := yaml.Marshal(crd) - if err != nil { - return nil, err - } // Remove the "status" field from yaml data, which causes a // resource creation error. - crdMap := make(map[string]interface{}) - if err = yaml.Unmarshal(b, &crdMap); err != nil { + crdMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(crd) + if err != nil { return nil, err } delete(crdMap, "status") From df3df61ecb3d82074b89e0bbcc7c0dc61768c9b8 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 9 Jan 2019 16:00:01 -0800 Subject: [PATCH 18/34] use Golang initialism/acronym conventions for naming CR(D) types/variables --- commands/operator-sdk/cmd/add.go | 2 +- commands/operator-sdk/cmd/add/api.go | 4 +- commands/operator-sdk/cmd/add/crd.go | 18 ++++---- commands/operator-sdk/cmd/generate/openapi.go | 10 ++--- commands/operator-sdk/cmd/new.go | 8 ++-- commands/operator-sdk/cmd/test/local.go | 6 +-- internal/util/yamlutil/manifest.go | 6 +-- pkg/scaffold/constants.go | 2 +- pkg/scaffold/cr.go | 8 ++-- pkg/scaffold/cr_test.go | 2 +- pkg/scaffold/crd.go | 44 +++++++++---------- pkg/scaffold/crd_test.go | 6 +-- 12 files changed, 58 insertions(+), 58 deletions(-) diff --git a/commands/operator-sdk/cmd/add.go b/commands/operator-sdk/cmd/add.go index f4f87be3ea..69d6f8ca3a 100644 --- a/commands/operator-sdk/cmd/add.go +++ b/commands/operator-sdk/cmd/add.go @@ -29,6 +29,6 @@ func NewAddCmd() *cobra.Command { upCmd.AddCommand(add.NewApiCmd()) upCmd.AddCommand(add.NewControllerCmd()) - upCmd.AddCommand(add.NewAddCrdCmd()) + upCmd.AddCommand(add.NewAddCRDCmd()) return upCmd } diff --git a/commands/operator-sdk/cmd/add/api.go b/commands/operator-sdk/cmd/add/api.go index 7199679676..416e2538c4 100644 --- a/commands/operator-sdk/cmd/add/api.go +++ b/commands/operator-sdk/cmd/add/api.go @@ -88,8 +88,8 @@ func apiRun(cmd *cobra.Command, args []string) { &scaffold.AddToScheme{Resource: r}, &scaffold.Register{Resource: r}, &scaffold.Doc{Resource: r}, - &scaffold.Cr{Resource: r}, - &scaffold.Crd{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, + &scaffold.CR{Resource: r}, + &scaffold.CRD{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, ) if err != nil { log.Fatalf("add scaffold failed: (%v)", err) diff --git a/commands/operator-sdk/cmd/add/crd.go b/commands/operator-sdk/cmd/add/crd.go index a5392909c5..50b3214f68 100644 --- a/commands/operator-sdk/cmd/add/crd.go +++ b/commands/operator-sdk/cmd/add/crd.go @@ -27,8 +27,8 @@ import ( "github.com/spf13/cobra" ) -// NewAddCrdCmd - add crd command -func NewAddCrdCmd() *cobra.Command { +// NewAddCRDCmd - add crd command +func NewAddCRDCmd() *cobra.Command { crdCmd := &cobra.Command{ Use: "crd", Short: "Adds a custom resource definition (CRD) and the custom resource (CR) files", @@ -56,8 +56,8 @@ func crdFunc(cmd *cobra.Command, args []string) { if len(args) != 0 { log.Fatal("crd command doesn't accept any arguments") } - verifyCrdFlags() - verifyCrdDeployPath() + verifyCRDFlags() + verifyCRDDeployPath() log.Infof("Generating Custom Resource Definition (CRD) version %s for kind %s.", apiVersion, kind) @@ -69,8 +69,8 @@ func crdFunc(cmd *cobra.Command, args []string) { s := scaffold.Scaffold{} err = s.Execute(cfg, - &scaffold.Crd{Resource: resource, IsOperatorGo: projutil.IsOperatorGo()}, - &scaffold.Cr{Resource: resource}, + &scaffold.CRD{Resource: resource, IsOperatorGo: projutil.IsOperatorGo()}, + &scaffold.CR{Resource: resource}, ) if err != nil { log.Fatalf("add scaffold failed: (%v)", err) @@ -84,7 +84,7 @@ func crdFunc(cmd *cobra.Command, args []string) { log.Info("CRD generation complete.") } -func verifyCrdFlags() { +func verifyCRDFlags() { if len(apiVersion) == 0 { log.Fatal("--api-version must not have empty value") } @@ -100,8 +100,8 @@ func verifyCrdFlags() { } } -// verifyCrdDeployPath checks if the path /deploy sub-directory is exists, and that is rooted under $GOPATH -func verifyCrdDeployPath() { +// verifyCRDDeployPath checks if the path /deploy sub-directory is exists, and that is rooted under $GOPATH +func verifyCRDDeployPath() { wd, err := os.Getwd() if err != nil { log.Fatalf("failed to determine the full path of the current directory: (%v)", err) diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 712b6fdb4e..29645358dc 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -89,7 +89,7 @@ func OpenAPIGen() { AbsProjectPath: absProjectPath, ProjectName: filepath.Base(absProjectPath), } - crdMap := getCrdGVKMap() + crdMap := getCRDGVKMap() for g, vs := range gvMap { for _, v := range vs { gvks := crdMap[filepath.Join(g, v)] @@ -99,7 +99,7 @@ func OpenAPIGen() { log.Fatal(err) } err = s.Execute(cfg, - &scaffold.Crd{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, + &scaffold.CRD{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, ) if err != nil { log.Fatal(err) @@ -135,15 +135,15 @@ func openAPIGen(binDir, headerFile string, fqApis []string) { } } -func getCrdGVKMap() map[string][]metav1.GroupVersionKind { - crdInfos, err := ioutil.ReadDir(scaffold.CrdsDir) +func getCRDGVKMap() map[string][]metav1.GroupVersionKind { + crdInfos, err := ioutil.ReadDir(scaffold.CRDsDir) if err != nil { log.Fatal(err) } crdMap := make(map[string][]metav1.GroupVersionKind) for _, info := range crdInfos { if filepath.Ext(info.Name()) == ".yaml" { - path := filepath.Join(scaffold.CrdsDir, info.Name()) + path := filepath.Join(scaffold.CRDsDir, info.Name()) b, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) diff --git a/commands/operator-sdk/cmd/new.go b/commands/operator-sdk/cmd/new.go index 86c2971736..8939ccf164 100644 --- a/commands/operator-sdk/cmd/new.go +++ b/commands/operator-sdk/cmd/new.go @@ -197,10 +197,10 @@ func doAnsibleScaffold() { &ansible.Operator{ IsClusterScoped: isClusterScoped, }, - &scaffold.Crd{ + &scaffold.CRD{ Resource: resource, }, - &scaffold.Cr{ + &scaffold.CR{ Resource: resource, }, ) @@ -271,10 +271,10 @@ func doHelmScaffold() { &helm.Operator{ IsClusterScoped: isClusterScoped, }, - &scaffold.Crd{ + &scaffold.CRD{ Resource: resource, }, - &scaffold.Cr{ + &scaffold.CR{ Resource: resource, }, ) diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index 79edf5570f..762735c384 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -128,16 +128,16 @@ func testLocalFunc(cmd *cobra.Command, args []string) { log.Fatalf("could not create %s: (%v)", deployTestDir, err) } tlConfig.globalManPath = filepath.Join(deployTestDir, "global-manifests.yaml") - files, err := ioutil.ReadDir(scaffold.CrdsDir) + files, err := ioutil.ReadDir(scaffold.CRDsDir) if err != nil { log.Fatalf("could not read deploy directory: (%v)", err) } var combined []byte for _, file := range files { if strings.HasSuffix(file.Name(), "crd.yaml") { - fileBytes, err := ioutil.ReadFile(filepath.Join(scaffold.CrdsDir, file.Name())) + fileBytes, err := ioutil.ReadFile(filepath.Join(scaffold.CRDsDir, file.Name())) if err != nil { - log.Fatalf("could not read file %s: (%v)", filepath.Join(scaffold.CrdsDir, file.Name()), err) + log.Fatalf("could not read file %s: (%v)", filepath.Join(scaffold.CRDsDir, file.Name()), err) } if combined == nil { combined = []byte{} diff --git a/internal/util/yamlutil/manifest.go b/internal/util/yamlutil/manifest.go index 5b0df75203..01a97abbbd 100644 --- a/internal/util/yamlutil/manifest.go +++ b/internal/util/yamlutil/manifest.go @@ -100,16 +100,16 @@ func GenerateCombinedGlobalManifest() (*os.File, error) { } defer file.Close() - files, err := ioutil.ReadDir(scaffold.CrdsDir) + files, err := ioutil.ReadDir(scaffold.CRDsDir) if err != nil { return nil, fmt.Errorf("could not read deploy directory: (%v)", err) } combined := []byte{} for _, file := range files { if strings.HasSuffix(file.Name(), "crd.yaml") { - fileBytes, err := ioutil.ReadFile(filepath.Join(scaffold.CrdsDir, file.Name())) + fileBytes, err := ioutil.ReadFile(filepath.Join(scaffold.CRDsDir, file.Name())) if err != nil { - return nil, fmt.Errorf("could not read file %s: (%v)", filepath.Join(scaffold.CrdsDir, file.Name()), err) + return nil, fmt.Errorf("could not read file %s: (%v)", filepath.Join(scaffold.CRDsDir, file.Name()), err) } combined = CombineManifests(combined, fileBytes) } diff --git a/pkg/scaffold/constants.go b/pkg/scaffold/constants.go index fdcbbccb06..d7def67e19 100644 --- a/pkg/scaffold/constants.go +++ b/pkg/scaffold/constants.go @@ -33,6 +33,6 @@ const ( BuildBinDir = BuildDir + filePathSep + "_output" + filePathSep + "bin" DeployDir = "deploy" OlmCatalogDir = DeployDir + filePathSep + "olm-catalog" - CrdsDir = DeployDir + filePathSep + "crds" + CRDsDir = DeployDir + filePathSep + "crds" VersionDir = "version" ) diff --git a/pkg/scaffold/cr.go b/pkg/scaffold/cr.go index 18d58cc03e..fb75f5b6cc 100644 --- a/pkg/scaffold/cr.go +++ b/pkg/scaffold/cr.go @@ -22,21 +22,21 @@ import ( "github.com/operator-framework/operator-sdk/pkg/scaffold/input" ) -// Cr is the input needed to generate a deploy/crds/___cr.yaml file -type Cr struct { +// CR is the input needed to generate a deploy/crds/___cr.yaml file +type CR struct { input.Input // Resource defines the inputs for the new custom resource Resource *Resource } -func (s *Cr) GetInput() (input.Input, error) { +func (s *CR) GetInput() (input.Input, error) { if s.Path == "" { fileName := fmt.Sprintf("%s_%s_%s_cr.yaml", strings.ToLower(s.Resource.Group), strings.ToLower(s.Resource.Version), s.Resource.LowerKind) - s.Path = filepath.Join(CrdsDir, fileName) + s.Path = filepath.Join(CRDsDir, fileName) } s.TemplateBody = crTemplate return s.Input, nil diff --git a/pkg/scaffold/cr_test.go b/pkg/scaffold/cr_test.go index fa8368088a..5b75db34ba 100644 --- a/pkg/scaffold/cr_test.go +++ b/pkg/scaffold/cr_test.go @@ -26,7 +26,7 @@ func TestCr(t *testing.T) { t.Fatal(err) } s, buf := setupScaffoldAndWriter() - err = s.Execute(appConfig, &Cr{Resource: r}) + err = s.Execute(appConfig, &CR{Resource: r}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) } diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index c0a21ea6ea..ef0d46b1d7 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -32,8 +32,8 @@ import ( crdgenerator "sigs.k8s.io/controller-tools/pkg/crd/generator" ) -// Crd is the input needed to generate a deploy/crds/___crd.yaml file -type Crd struct { +// CRD is the input needed to generate a deploy/crds/___crd.yaml file +type CRD struct { input.Input // Resource defines the inputs for the new custom resource definition @@ -43,13 +43,13 @@ type Crd struct { IsOperatorGo bool } -func (s *Crd) GetInput() (input.Input, error) { +func (s *CRD) GetInput() (input.Input, error) { if s.Path == "" { fileName := fmt.Sprintf("%s_%s_%s_crd.yaml", strings.ToLower(s.Resource.Group), strings.ToLower(s.Resource.Version), s.Resource.LowerKind) - s.Path = filepath.Join(CrdsDir, fileName) + s.Path = filepath.Join(CRDsDir, fileName) } initCache() return s.Input, nil @@ -65,7 +65,7 @@ func (c *fsCache) fileExists(path string) bool { } var ( - // Global cache so users can use new Crd structs. + // Global cache so users can use new CRD structs. cache *fsCache once sync.Once ) @@ -76,7 +76,7 @@ func initCache() { }) } -func (s *Crd) CustomRender() ([]byte, error) { +func (s *CRD) CustomRender() ([]byte, error) { i, _ := s.GetInput() // controller-tools generates crd file names with no _crd.yaml suffix: // __.yaml. @@ -100,7 +100,7 @@ func (s *Crd) CustomRender() ([]byte, error) { } } - dstCrd := newCrdForResource(s.Resource) + dstCRD := newCRDForResource(s.Resource) // Get our generated crd's from the in-memory fs. If it doesn't exist in the // fs, the corresponding API does not exist yet, so scaffold a fresh crd // without a validation spec. @@ -113,11 +113,11 @@ func (s *Crd) CustomRender() ([]byte, error) { if err != nil { return nil, err } - dstCrd = new(apiextv1beta1.CustomResourceDefinition) - if err = yaml.Unmarshal(b, dstCrd); err != nil { + dstCRD = new(apiextv1beta1.CustomResourceDefinition) + if err = yaml.Unmarshal(b, dstCRD); err != nil { return nil, err } - val := dstCrd.Spec.Validation.DeepCopy() + val := dstCRD.Spec.Validation.DeepCopy() // If the crd exists at i.Path, append the validation spec to its crd spec. if _, err := os.Stat(i.Path); err == nil { @@ -126,23 +126,23 @@ func (s *Crd) CustomRender() ([]byte, error) { return nil, err } if len(cb) > 0 { - dstCrd = new(apiextv1beta1.CustomResourceDefinition) - if err = yaml.Unmarshal(cb, dstCrd); err != nil { + dstCRD = new(apiextv1beta1.CustomResourceDefinition) + if err = yaml.Unmarshal(cb, dstCRD); err != nil { return nil, err } - dstCrd.Spec.Validation = val + dstCRD.Spec.Validation = val } } // controller-tools does not set ListKind or Singular names. - dstCrd.Spec.Names = getCrdNamesForResource(s.Resource) + dstCRD.Spec.Names = getCRDNamesForResource(s.Resource) // Remove controller-tools default label. - delete(dstCrd.Labels, "controller-tools.k8s.io") + delete(dstCRD.Labels, "controller-tools.k8s.io") } - addCrdSubresource(dstCrd) - return getCrdBytes(dstCrd) + addCRDSubresource(dstCRD) + return getCRDBytes(dstCRD) } -func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { +func newCRDForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { return &apiextv1beta1.CustomResourceDefinition{ TypeMeta: metav1.TypeMeta{ APIVersion: "apiextensions.k8s.io/v1beta1", @@ -153,7 +153,7 @@ func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { }, Spec: apiextv1beta1.CustomResourceDefinitionSpec{ Group: r.FullGroup, - Names: getCrdNamesForResource(r), + Names: getCRDNamesForResource(r), Scope: apiextv1beta1.NamespaceScoped, Version: r.Version, Subresources: &apiextv1beta1.CustomResourceSubresources{ @@ -163,7 +163,7 @@ func newCrdForResource(r *Resource) *apiextv1beta1.CustomResourceDefinition { } } -func getCrdNamesForResource(r *Resource) apiextv1beta1.CustomResourceDefinitionNames { +func getCRDNamesForResource(r *Resource) apiextv1beta1.CustomResourceDefinitionNames { return apiextv1beta1.CustomResourceDefinitionNames{ Kind: r.Kind, ListKind: r.Kind + "List", @@ -172,7 +172,7 @@ func getCrdNamesForResource(r *Resource) apiextv1beta1.CustomResourceDefinitionN } } -func addCrdSubresource(crd *apiextv1beta1.CustomResourceDefinition) { +func addCRDSubresource(crd *apiextv1beta1.CustomResourceDefinition) { if crd.Spec.Subresources == nil { crd.Spec.Subresources = &apiextv1beta1.CustomResourceSubresources{} } @@ -181,7 +181,7 @@ func addCrdSubresource(crd *apiextv1beta1.CustomResourceDefinition) { } } -func getCrdBytes(crd *apiextv1beta1.CustomResourceDefinition) ([]byte, error) { +func getCRDBytes(crd *apiextv1beta1.CustomResourceDefinition) ([]byte, error) { // Remove the "status" field from yaml data, which causes a // resource creation error. crdMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(crd) diff --git a/pkg/scaffold/crd_test.go b/pkg/scaffold/crd_test.go index bababef175..5f27e9797c 100644 --- a/pkg/scaffold/crd_test.go +++ b/pkg/scaffold/crd_test.go @@ -47,7 +47,7 @@ func TestCRDGoProject(t *testing.T) { t.Fatal(err) } defer func() { os.Chdir(absPath) }() - err = s.Execute(cfg, &Crd{Resource: r, IsOperatorGo: true}) + err = s.Execute(cfg, &CRD{Resource: r, IsOperatorGo: true}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) } @@ -102,13 +102,13 @@ spec: version: v1alpha1 ` -func TestCrdNonGoProject(t *testing.T) { +func TestCRDNonGoProject(t *testing.T) { r, err := NewResource(appApiVersion, appKind) if err != nil { t.Fatal(err) } s, buf := setupScaffoldAndWriter() - err = s.Execute(appConfig, &Crd{Resource: r}) + err = s.Execute(appConfig, &CRD{Resource: r}) if err != nil { t.Fatalf("failed to execute the scaffold: (%v)", err) } From d15010ca9c5cc63d3605d9256079be22bc778aa5 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 15 Jan 2019 15:38:31 -0800 Subject: [PATCH 19/34] Gopkg.lock: revendor --- Gopkg.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c6e2027876..fc63dd1127 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -567,10 +567,10 @@ version = "v2.0.1" [[projects]] - digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] - pruneopts = "" + pruneopts = "UT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" @@ -1175,7 +1175,7 @@ [[projects]] branch = "master" - digest = "1:0cc7d194e005097afad585545a3049ac7b5d1e3a1bfa86aa6bf6f2fb0e0a5063" + digest = "1:4defc5df1acbcc491730533734e94a8571ad8c005ca70b71e9697113a27a0166" name = "k8s.io/gengo" packages = [ "args", @@ -1184,7 +1184,7 @@ "parser", "types", ] - pruneopts = "" + pruneopts = "UT" revision = "fd15ee9cc2f77baa4f31e59e6acbf21146455073" [[projects]] @@ -1219,10 +1219,10 @@ version = "v2.12.0" [[projects]] - digest = "1:4f5eb833037cc0ba0bf8fe9cae6be9df62c19dd1c869415275c708daa8ccfda5" + digest = "1:e2999bf1bb6eddc2a6aa03fe5e6629120a53088926520ca3b4765f77d7ff7eab" name = "k8s.io/klog" packages = ["."] - pruneopts = "" + pruneopts = "UT" revision = "a5bc97fbc634d635061f3146511332c7e313a55a" version = "v0.1.0" @@ -1437,7 +1437,7 @@ version = "v0.1.8" [[projects]] - digest = "1:b44bf3e4adc37dfebcd1a3715d472509e0f4cf0ac24038cd7fc5654f176bd420" + digest = "1:682237faf8a777ed193912b47f933c5c900829ce01079da3f41455537c0e471a" name = "sigs.k8s.io/controller-tools" packages = [ "pkg/crd/generator", @@ -1447,7 +1447,7 @@ "pkg/internal/general", "pkg/util", ] - pruneopts = "" + pruneopts = "UT" revision = "b072ef59824b16023b0e12c94d0040d99059a961" version = "v0.1.7" From 394b75651c400bbb9448d6f149d29ccd2ce12ae7 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 15 Jan 2019 15:38:52 -0800 Subject: [PATCH 20/34] commands/.../openapi.go: return error from CLI func instead of log.Fatal --- commands/operator-sdk/cmd/generate/openapi.go | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index a4376c71d7..4b305e2db8 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -40,22 +40,22 @@ func NewGenerateOpenAPICmd() *cobra.Command { Long: ` generate openapi generates OpenAPI validation specs in Go from types in all pkg/apis// directories. `, - Run: openAPIFunc, + RunE: openAPIFunc, } return openAPICmd } -func openAPIFunc(cmd *cobra.Command, args []string) { +func openAPIFunc(cmd *cobra.Command, args []string) error { if len(args) != 0 { - log.Fatalf("Command %s doesn't accept any arguments", cmd.CommandPath()) + return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) } - OpenAPIGen() + return OpenAPIGen() } // OpenAPIGen generates OpenAPI validation specs for all CRD's in dirs. -func OpenAPIGen() { +func OpenAPIGen() error { projutil.MustInProjectRoot() absProjectPath := projutil.MustGetwd() @@ -65,11 +65,13 @@ func OpenAPIGen() { bpFile := filepath.Join(vendor, "k8s.io", "gengo", "boilerplate", "boilerplate.go.txt") binDir := filepath.Join(absProjectPath, scaffold.BuildBinDir) - buildOpenAPIGenBinary(binDir, srcDir) + if err := buildOpenAPIGenBinary(binDir, srcDir); err != nil { + return err + } gvMap, err := genutil.ParseGroupVersions() if err != nil { - log.Fatalf("Failed to parse group versions: (%v)", err) + return fmt.Errorf("failed to parse group versions: (%v)", err) } gvb := &strings.Builder{} for g, vs := range gvMap { @@ -81,7 +83,9 @@ func OpenAPIGen() { apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) fqApiStr := genutil.CreateFQApis(apisPkg, gvMap) fqApis := strings.Split(fqApiStr, ",") - openAPIGen(binDir, bpFile, fqApis) + if err := openAPIGen(binDir, bpFile, fqApis); err != nil { + return err + } s := &scaffold.Scaffold{} cfg := &input.Config{ @@ -89,37 +93,38 @@ func OpenAPIGen() { AbsProjectPath: absProjectPath, ProjectName: filepath.Base(absProjectPath), } - crdMap := getCRDGVKMap() + crdMap, err := getCRDGVKMap() + if err != nil { + return err + } for g, vs := range gvMap { for _, v := range vs { gvks := crdMap[filepath.Join(g, v)] for _, gvk := range gvks { r, err := scaffold.NewResource(filepath.Join(gvk.Group, gvk.Version), gvk.Kind) if err != nil { - log.Fatal(err) + return err } err = s.Execute(cfg, &scaffold.CRD{Resource: r, IsOperatorGo: projutil.IsOperatorGo()}, ) if err != nil { - log.Fatal(err) + return err } } } } log.Info("Code-generation complete.") + return nil } -func buildOpenAPIGenBinary(binDir, codegenSrcDir string) { +func buildOpenAPIGenBinary(binDir, codegenSrcDir string) error { genDirs := []string{"./cmd/openapi-gen"} - err := genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) - if err != nil { - log.Fatal(err) - } + return genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) } -func openAPIGen(binDir, headerFile string, fqApis []string) { +func openAPIGen(binDir, headerFile string, fqApis []string) error { cgPath := filepath.Join(binDir, "openapi-gen") for _, fqApi := range fqApis { args := []string{ @@ -130,15 +135,16 @@ func openAPIGen(binDir, headerFile string, fqApis []string) { } err := projutil.ExecCmd(exec.Command(cgPath, args...)) if err != nil { - log.Fatalf("Failed to perform code-generation: %v", err) + return fmt.Errorf("failed to perform code-generation: %v", err) } } + return nil } -func getCRDGVKMap() map[string][]metav1.GroupVersionKind { +func getCRDGVKMap() (map[string][]metav1.GroupVersionKind, error) { crdInfos, err := ioutil.ReadDir(scaffold.CRDsDir) if err != nil { - log.Fatal(err) + return nil, err } crdMap := make(map[string][]metav1.GroupVersionKind) for _, info := range crdInfos { @@ -146,11 +152,11 @@ func getCRDGVKMap() map[string][]metav1.GroupVersionKind { path := filepath.Join(scaffold.CRDsDir, info.Name()) b, err := ioutil.ReadFile(path) if err != nil { - log.Fatal(err) + return nil, err } crd := new(apiextv1beta1.CustomResourceDefinition) if err := yaml.Unmarshal(b, crd); err != nil { - log.Fatal(err) + return nil, err } if crd.Kind != "CustomResourceDefinition" { continue @@ -163,5 +169,5 @@ func getCRDGVKMap() map[string][]metav1.GroupVersionKind { }) } } - return crdMap + return crdMap, nil } From fbec75b1cf0b5240d678d57d730bb6c2aad12317 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 22 Jan 2019 10:44:55 -0800 Subject: [PATCH 21/34] fix kube-openapi revision --- pkg/scaffold/gopkgtoml.go | 2 +- pkg/scaffold/gopkgtoml_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/scaffold/gopkgtoml.go b/pkg/scaffold/gopkgtoml.go index 6d05151acb..b43d6cd785 100644 --- a/pkg/scaffold/gopkgtoml.go +++ b/pkg/scaffold/gopkgtoml.go @@ -60,7 +60,7 @@ required = [ [[override]] name = "k8s.io/kube-openapi" - branch = "master" + revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" [[override]] name = "k8s.io/api" diff --git a/pkg/scaffold/gopkgtoml_test.go b/pkg/scaffold/gopkgtoml_test.go index 1dff9c2922..7953e543a7 100644 --- a/pkg/scaffold/gopkgtoml_test.go +++ b/pkg/scaffold/gopkgtoml_test.go @@ -52,7 +52,7 @@ required = [ [[override]] name = "k8s.io/kube-openapi" - branch = "master" + revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" [[override]] name = "k8s.io/api" From b5e915dc269daac93360fe24242fd648678252d2 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 22 Jan 2019 11:18:55 -0800 Subject: [PATCH 22/34] pkg/scaffold/gopkgtoml*.go: force sigs.k8s.io/controller-tools/pkg/crd/generator vendoring --- pkg/scaffold/gopkgtoml.go | 5 +++++ pkg/scaffold/gopkgtoml_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/pkg/scaffold/gopkgtoml.go b/pkg/scaffold/gopkgtoml.go index 9b7c5a8f5f..df48646c26 100644 --- a/pkg/scaffold/gopkgtoml.go +++ b/pkg/scaffold/gopkgtoml.go @@ -51,6 +51,7 @@ required = [ "k8s.io/code-generator/cmd/informer-gen", "k8s.io/kube-openapi/cmd/openapi-gen", "k8s.io/gengo/args", + "sigs.k8s.io/controller-tools/pkg/crd/generator", ] [[override]] @@ -62,6 +63,10 @@ required = [ name = "k8s.io/kube-openapi" revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" +[[override]] + name = "sigs.k8s.io/controller-tools" + version = "=v0.1.8" + [[override]] name = "k8s.io/api" # revision for tag "kubernetes-1.12.3" diff --git a/pkg/scaffold/gopkgtoml_test.go b/pkg/scaffold/gopkgtoml_test.go index 896e2fef43..7a912a6416 100644 --- a/pkg/scaffold/gopkgtoml_test.go +++ b/pkg/scaffold/gopkgtoml_test.go @@ -43,6 +43,7 @@ required = [ "k8s.io/code-generator/cmd/informer-gen", "k8s.io/kube-openapi/cmd/openapi-gen", "k8s.io/gengo/args", + "sigs.k8s.io/controller-tools/pkg/crd/generator", ] [[override]] @@ -54,6 +55,10 @@ required = [ name = "k8s.io/kube-openapi" revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" +[[override]] + name = "sigs.k8s.io/controller-tools" + version = "=v0.1.8" + [[override]] name = "k8s.io/api" # revision for tag "kubernetes-1.12.3" From 64688b0ce421654d00fac0da203d3f29faac5deb Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 25 Jan 2019 14:46:43 -0800 Subject: [PATCH 23/34] remove extra kube-openapi override --- pkg/scaffold/gopkgtoml.go | 4 ---- pkg/scaffold/gopkgtoml_test.go | 4 ---- 2 files changed, 8 deletions(-) diff --git a/pkg/scaffold/gopkgtoml.go b/pkg/scaffold/gopkgtoml.go index 7a78847050..df48646c26 100644 --- a/pkg/scaffold/gopkgtoml.go +++ b/pkg/scaffold/gopkgtoml.go @@ -101,10 +101,6 @@ required = [ branch = "master" #osdk_branch_annotation # version = "=v0.4.0" #osdk_version_annotation -[[override]] - name = "k8s.io/kube-openapi" - revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" - [prune] go-tests = true non-go = true diff --git a/pkg/scaffold/gopkgtoml_test.go b/pkg/scaffold/gopkgtoml_test.go index 4cf6cdb11d..7a912a6416 100644 --- a/pkg/scaffold/gopkgtoml_test.go +++ b/pkg/scaffold/gopkgtoml_test.go @@ -93,10 +93,6 @@ required = [ branch = "master" #osdk_branch_annotation # version = "=v0.4.0" #osdk_version_annotation -[[override]] - name = "k8s.io/kube-openapi" - revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" - [prune] go-tests = true non-go = true From e38a2f441fef0f43ddefcb3fcf277a5b9cebd525 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 25 Jan 2019 16:00:12 -0800 Subject: [PATCH 24/34] pkg/scaffold/types*.go: add note on adding custom validation --- pkg/scaffold/types.go | 2 ++ pkg/scaffold/types_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/scaffold/types.go b/pkg/scaffold/types.go index 91965f04e7..c4bacca9f7 100644 --- a/pkg/scaffold/types.go +++ b/pkg/scaffold/types.go @@ -56,6 +56,7 @@ import ( type {{.Resource.Kind}}Spec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html } // {{.Resource.Kind}}Status defines the observed state of {{.Resource.Kind}} @@ -63,6 +64,7 @@ type {{.Resource.Kind}}Spec struct { type {{.Resource.Kind}}Status struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/scaffold/types_test.go b/pkg/scaffold/types_test.go index aad2421129..b0c3bc6838 100644 --- a/pkg/scaffold/types_test.go +++ b/pkg/scaffold/types_test.go @@ -51,6 +51,7 @@ import ( type AppServiceSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html } // AppServiceStatus defines the observed state of AppService @@ -58,6 +59,7 @@ type AppServiceSpec struct { type AppServiceStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object From 92b416f26eda24a93a8a85d671460451611bf530 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 25 Jan 2019 16:29:34 -0800 Subject: [PATCH 25/34] commands/.../api.go: comment on what is generated in add api command usage --- commands/operator-sdk/cmd/add/api.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/add/api.go b/commands/operator-sdk/cmd/add/api.go index 039dbcf0da..4b3aa27d09 100644 --- a/commands/operator-sdk/cmd/add/api.go +++ b/commands/operator-sdk/cmd/add/api.go @@ -36,8 +36,17 @@ func NewApiCmd() *cobra.Command { Use: "api", Short: "Adds a new api definition under pkg/apis", Long: `operator-sdk add api --kind= --api-version= creates the -api definition for a new custom resource under pkg/apis. This command must be run from the project root directory. -If the api already exists at pkg/apis// then the command will not overwrite and return an error. +api definition for a new custom resource under pkg/apis. This command must be +run from the project root directory. If the api already exists at +pkg/apis// then the command will not overwrite and return an +error. + +This command runs Kubernetes deepcopy and OpenAPI V3 generators on tagged +types in all paths under pkg/apis. Go code is generated under +pkg/apis///zz_generated.{deepcopy,openapi}.go. CRD's are +generated, or updated if they exist for a particular group + version + kind, +under deploy/crds/___crd.yaml; OpenAPI V3 validation YAML +is generated as a 'validation' object. Example: $ operator-sdk add api --api-version=app.example.com/v1alpha1 --kind=AppService @@ -49,8 +58,12 @@ Example: └── v1alpha1 ├── doc.go ├── register.go - ├── types.go - + ├── appservice_types.go + ├── zz_generated.deepcopy.go + ├── zz_generated.openapi.go + $ tree deploy/crds + ├── deploy/crds/app_v1alpha1_appservice_cr.yaml + ├── deploy/crds/app_v1alpha1_appservice_crd.yaml `, RunE: apiRun, } From 3c379d0fc42dd4c563d5bee0104b089ca051c1a3 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 28 Jan 2019 11:35:55 -0800 Subject: [PATCH 26/34] commands/.../openapi.go: remove go header file from openapi-gen --- commands/operator-sdk/cmd/generate/openapi.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 4b305e2db8..178034e614 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -60,9 +60,7 @@ func OpenAPIGen() error { absProjectPath := projutil.MustGetwd() repoPkg := projutil.CheckAndGetProjectGoPkg() - vendor := filepath.Join(absProjectPath, "vendor") - srcDir := filepath.Join(vendor, "k8s.io", "kube-openapi") - bpFile := filepath.Join(vendor, "k8s.io", "gengo", "boilerplate", "boilerplate.go.txt") + srcDir := filepath.Join(absProjectPath, "vendor", "k8s.io", "kube-openapi") binDir := filepath.Join(absProjectPath, scaffold.BuildBinDir) if err := buildOpenAPIGenBinary(binDir, srcDir); err != nil { @@ -83,7 +81,7 @@ func OpenAPIGen() error { apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) fqApiStr := genutil.CreateFQApis(apisPkg, gvMap) fqApis := strings.Split(fqApiStr, ",") - if err := openAPIGen(binDir, bpFile, fqApis); err != nil { + if err := openAPIGen(binDir, fqApis); err != nil { return err } @@ -124,14 +122,13 @@ func buildOpenAPIGenBinary(binDir, codegenSrcDir string) error { return genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) } -func openAPIGen(binDir, headerFile string, fqApis []string) error { +func openAPIGen(binDir string, fqApis []string) error { cgPath := filepath.Join(binDir, "openapi-gen") for _, fqApi := range fqApis { args := []string{ "--input-dirs", fqApi, "--output-package", fqApi, "--output-file-base", "zz_generated.openapi", - "--go-header-file", headerFile, } err := projutil.ExecCmd(exec.Command(cgPath, args...)) if err != nil { From 334156e84c5de5ef8c8b8dd7bff0693ef802dfec Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 28 Jan 2019 11:42:09 -0800 Subject: [PATCH 27/34] new() -> &...{} --- commands/operator-sdk/cmd/generate/openapi.go | 2 +- pkg/scaffold/crd.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 178034e614..774933aae3 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -151,7 +151,7 @@ func getCRDGVKMap() (map[string][]metav1.GroupVersionKind, error) { if err != nil { return nil, err } - crd := new(apiextv1beta1.CustomResourceDefinition) + crd := &apiextv1beta1.CustomResourceDefinition{} if err := yaml.Unmarshal(b, crd); err != nil { return nil, err } diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index ef0d46b1d7..30148e530a 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -113,7 +113,7 @@ func (s *CRD) CustomRender() ([]byte, error) { if err != nil { return nil, err } - dstCRD = new(apiextv1beta1.CustomResourceDefinition) + dstCRD = &apiextv1beta1.CustomResourceDefinition{} if err = yaml.Unmarshal(b, dstCRD); err != nil { return nil, err } @@ -126,7 +126,7 @@ func (s *CRD) CustomRender() ([]byte, error) { return nil, err } if len(cb) > 0 { - dstCRD = new(apiextv1beta1.CustomResourceDefinition) + dstCRD = &apiextv1beta1.CustomResourceDefinition{} if err = yaml.Unmarshal(cb, dstCRD); err != nil { return nil, err } From d0cbe0c7c4b0e1ffab9a1362bc0199161089b274 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 28 Jan 2019 13:07:38 -0800 Subject: [PATCH 28/34] correct verbosity settings for deepcopy and openapi generators --- .../cmd/generate/internal/genutil.go | 26 ++++++++----------- commands/operator-sdk/cmd/generate/k8s.go | 15 ++++++++--- commands/operator-sdk/cmd/generate/openapi.go | 13 +++++++--- internal/util/projutil/project_util.go | 15 +++++++++-- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/commands/operator-sdk/cmd/generate/internal/genutil.go b/commands/operator-sdk/cmd/generate/internal/genutil.go index a284152b69..a98aadd548 100644 --- a/commands/operator-sdk/cmd/generate/internal/genutil.go +++ b/commands/operator-sdk/cmd/generate/internal/genutil.go @@ -22,6 +22,7 @@ import ( "path/filepath" "strings" + "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/pkg/scaffold" ) @@ -37,23 +38,18 @@ func BuildCodegenBinaries(genDirs []string, binDir, codegenSrcDir string) error func runGoBuildCodegen(binDir, repoDir, genDir string) error { binPath := filepath.Join(binDir, filepath.Base(genDir)) - installCmd := exec.Command("go", "build", "-o", binPath, genDir) - installCmd.Dir = repoDir - isVerbose := false - if gf, ok := os.LookupEnv("GOFLAGS"); ok && len(gf) != 0 { - installCmd.Env = append(os.Environ(), "GOFLAGS="+gf) - if strings.Contains(gf, "-v") { - isVerbose = true - } + cmd := exec.Command("go", "build", "-o", binPath, genDir) + cmd.Dir = repoDir + if gf, ok := os.LookupEnv(projutil.GoFlagsEnv); ok && len(gf) != 0 { + cmd.Env = append(os.Environ(), projutil.GoFlagsEnv+"="+gf) } - if isVerbose { - installCmd.Stdout = os.Stdout - installCmd.Stderr = os.Stderr - } else { - installCmd.Stdout = ioutil.Discard - installCmd.Stderr = ioutil.Discard + + if projutil.IsGoVerbose() { + return projutil.ExecCmd(cmd) } - return installCmd.Run() + cmd.Stdout = ioutil.Discard + cmd.Stderr = ioutil.Discard + return cmd.Run() } // ParseGroupVersions parses the layout of pkg/apis to return a map of diff --git a/commands/operator-sdk/cmd/generate/k8s.go b/commands/operator-sdk/cmd/generate/k8s.go index 593b9db398..1779e7430f 100644 --- a/commands/operator-sdk/cmd/generate/k8s.go +++ b/commands/operator-sdk/cmd/generate/k8s.go @@ -16,6 +16,7 @@ package generate import ( "fmt" + "io/ioutil" "os/exec" "path/filepath" "strings" @@ -95,17 +96,23 @@ func buildCodegenBinaries(binDir, codegenSrcDir string) error { return genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) } -func deepcopyGen(binDir, repoPkg string, gvMap map[string][]string) error { +func deepcopyGen(binDir, repoPkg string, gvMap map[string][]string) (err error) { apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) args := []string{ "--input-dirs", genutil.CreateFQApis(apisPkg, gvMap), "--output-file-base", "zz_generated.deepcopy", "--bounding-dirs", apisPkg, } - cgPath := filepath.Join(binDir, "deepcopy-gen") - err := projutil.ExecCmd(exec.Command(cgPath, args...)) + cmd := exec.Command(filepath.Join(binDir, "deepcopy-gen"), args...) + if projutil.IsGoVerbose() { + err = projutil.ExecCmd(cmd) + } else { + cmd.Stdout = ioutil.Discard + cmd.Stderr = ioutil.Discard + err = cmd.Run() + } if err != nil { - return fmt.Errorf("failed to perform code-generation: %v", err) + return fmt.Errorf("failed to perform deepcopy code-generation: %v", err) } return nil } diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 774933aae3..1412784b8c 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -122,7 +122,7 @@ func buildOpenAPIGenBinary(binDir, codegenSrcDir string) error { return genutil.BuildCodegenBinaries(genDirs, binDir, codegenSrcDir) } -func openAPIGen(binDir string, fqApis []string) error { +func openAPIGen(binDir string, fqApis []string) (err error) { cgPath := filepath.Join(binDir, "openapi-gen") for _, fqApi := range fqApis { args := []string{ @@ -130,9 +130,16 @@ func openAPIGen(binDir string, fqApis []string) error { "--output-package", fqApi, "--output-file-base", "zz_generated.openapi", } - err := projutil.ExecCmd(exec.Command(cgPath, args...)) + cmd := exec.Command(cgPath, args...) + if projutil.IsGoVerbose() { + err = projutil.ExecCmd(cmd) + } else { + cmd.Stdout = ioutil.Discard + cmd.Stderr = ioutil.Discard + err = cmd.Run() + } if err != nil { - return fmt.Errorf("failed to perform code-generation: %v", err) + return fmt.Errorf("failed to perform openapi code-generation: %v", err) } } return nil diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 532ebff9b1..bc30189a25 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -19,6 +19,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "github.com/operator-framework/operator-sdk/pkg/scaffold" @@ -30,8 +31,9 @@ import ( ) const ( - GopathEnv = "GOPATH" - SrcDir = "src" + GopathEnv = "GOPATH" + GoFlagsEnv = "GOFLAGS" + SrcDir = "src" ) var mainFile = filepath.Join(scaffold.ManagerDir, scaffold.CmdFile) @@ -157,3 +159,12 @@ func ExecCmd(cmd *exec.Cmd) error { } return nil } + +var flagRe = regexp.MustCompile("(.* )?-v(.* )?") + +// IsGoVerbose returns true if GOFLAGS contains "-v". This function is useful +// when deciding whether to make "go" command output verbose. +func IsGoVerbose() bool { + gf, ok := os.LookupEnv(GoFlagsEnv) + return ok && len(gf) != 0 && flagRe.MatchString(gf) +} From 2d4b6214eac47a1275ce93250eedb467ecf850c2 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 28 Jan 2019 13:58:04 -0800 Subject: [PATCH 29/34] commands/operator-sdk/cmd/add/crd.go: only skip overwriting CRD's/CR's if explicitly adding them --- commands/operator-sdk/cmd/add/crd.go | 10 ++++++++-- pkg/scaffold/cr.go | 1 - pkg/scaffold/crd.go | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/add/crd.go b/commands/operator-sdk/cmd/add/crd.go index a788d4a0f3..1a7828d201 100644 --- a/commands/operator-sdk/cmd/add/crd.go +++ b/commands/operator-sdk/cmd/add/crd.go @@ -78,8 +78,14 @@ func crdFunc(cmd *cobra.Command, args []string) error { s := scaffold.Scaffold{} err = s.Execute(cfg, - &scaffold.Crd{Resource: resource}, - &scaffold.Cr{Resource: resource}, + &scaffold.Crd{ + Input: input.Input{IfExistsAction: input.Skip}, + Resource: resource, + }, + &scaffold.Cr{ + Input: input.Input{IfExistsAction: input.Skip}, + Resource: resource, + }, ) if err != nil { return fmt.Errorf("crd scaffold failed: (%v)", err) diff --git a/pkg/scaffold/cr.go b/pkg/scaffold/cr.go index d0cbf669f5..18d58cc03e 100644 --- a/pkg/scaffold/cr.go +++ b/pkg/scaffold/cr.go @@ -38,7 +38,6 @@ func (s *Cr) GetInput() (input.Input, error) { s.Resource.LowerKind) s.Path = filepath.Join(CrdsDir, fileName) } - s.IfExistsAction = input.Skip s.TemplateBody = crTemplate return s.Input, nil } diff --git a/pkg/scaffold/crd.go b/pkg/scaffold/crd.go index b6dfcbc477..a1a386b86d 100644 --- a/pkg/scaffold/crd.go +++ b/pkg/scaffold/crd.go @@ -38,7 +38,6 @@ func (s *Crd) GetInput() (input.Input, error) { s.Resource.LowerKind) s.Path = filepath.Join(CrdsDir, fileName) } - s.IfExistsAction = input.Skip s.TemplateBody = crdTemplate return s.Input, nil } From cc3695837fa5e82ceb5eeb0b1b8991b2521abaf4 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 29 Jan 2019 12:11:26 -0800 Subject: [PATCH 30/34] verbose code generation in e2e test --- test/e2e/memcached_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index a956aa2af9..4c9a847cf6 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -153,11 +153,17 @@ func TestMemcached(t *testing.T) { // t.Fatalf("Could not write deploy/operator.yaml: %v", err) // } - cmdOut, err = exec.Command("operator-sdk", + cmd := exec.Command("operator-sdk", "add", "api", "--api-version=cache.example.com/v1alpha1", - "--kind=Memcached").CombinedOutput() + "--kind=Memcached") + // Generators will print errors if -v is set. + if !projutil.IsGoVerbose() { + os.Setenv(projutil.GoFlagsEnv, os.Getenv(projutil.GoFlagsEnv)+" -v") + } + cmd.Env = os.Environ() + cmdOut, err = cmd.CombinedOutput() if err != nil { t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } From 93229be7dcc9d6850877ba8ef340fdc6a2350df0 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 30 Jan 2019 10:39:43 -0800 Subject: [PATCH 31/34] add --header-file for Go boilerplate file path --- commands/operator-sdk/cmd/generate/openapi.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 1412784b8c..388e96fc19 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -17,6 +17,7 @@ package generate import ( "fmt" "io/ioutil" + "os" "os/exec" "path/filepath" "strings" @@ -33,6 +34,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +var headerFile string + func NewGenerateOpenAPICmd() *cobra.Command { openAPICmd := &cobra.Command{ Use: "openapi", @@ -43,6 +46,8 @@ all pkg/apis// directories. RunE: openAPIFunc, } + openAPICmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated files.") + return openAPICmd } @@ -123,12 +128,27 @@ func buildOpenAPIGenBinary(binDir, codegenSrcDir string) error { } func openAPIGen(binDir string, fqApis []string) (err error) { + if headerFile == "" { + f, err := ioutil.TempFile(scaffold.BuildBinDir, "") + if err != nil { + return err + } + headerFile = f.Name() + defer func() { + if err = os.RemoveAll(headerFile); err != nil { + log.Error(err) + } + }() + } cgPath := filepath.Join(binDir, "openapi-gen") for _, fqApi := range fqApis { args := []string{ "--input-dirs", fqApi, "--output-package", fqApi, "--output-file-base", "zz_generated.openapi", + // openapi-gen requires a boilerplate file. Either use header or an + // empty file if header is empty. + "--go-header-file", headerFile, } cmd := exec.Command(cgPath, args...) if projutil.IsGoVerbose() { From b46c19a310f71370e45cc62f76e7d769e68a45b5 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 30 Jan 2019 10:40:08 -0800 Subject: [PATCH 32/34] update openapi command comment --- commands/operator-sdk/cmd/generate/k8s.go | 14 ++++++++++++-- commands/operator-sdk/cmd/generate/openapi.go | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/generate/k8s.go b/commands/operator-sdk/cmd/generate/k8s.go index 1779e7430f..9dcfeb9b4d 100644 --- a/commands/operator-sdk/cmd/generate/k8s.go +++ b/commands/operator-sdk/cmd/generate/k8s.go @@ -33,8 +33,18 @@ func NewGenerateK8SCmd() *cobra.Command { return &cobra.Command{ Use: "k8s", Short: "Generates Kubernetes code for custom resource", - Long: `k8s generator generates code for custom resource given the API spec -to comply with kube-API requirements. + Long: `k8s generator generates code for custom resources given the API +specs in pkg/apis// directories to comply with kube-API +requirements. Go code is generated under +pkg/apis///zz_generated.deepcopy.go. + +Example: + $ operator-sdk generate k8s + $ tree pkg/apis + pkg/apis/ + └── app + └── v1alpha1 + ├── zz_generated.deepcopy.go `, RunE: k8sFunc, } diff --git a/commands/operator-sdk/cmd/generate/openapi.go b/commands/operator-sdk/cmd/generate/openapi.go index 388e96fc19..0b7fdcacec 100644 --- a/commands/operator-sdk/cmd/generate/openapi.go +++ b/commands/operator-sdk/cmd/generate/openapi.go @@ -40,8 +40,23 @@ func NewGenerateOpenAPICmd() *cobra.Command { openAPICmd := &cobra.Command{ Use: "openapi", Short: "Generates OpenAPI specs for API's", - Long: ` generate openapi generates OpenAPI validation specs in Go from types in -all pkg/apis// directories. + Long: `generate openapi generates OpenAPI validation specs in Go from tagged types +in all pkg/apis// directories. Go code is generated under +pkg/apis///zz_generated.openapi.go. CRD's are generated, or +updated if they exist for a particular group + version + kind, under +deploy/crds/___crd.yaml; OpenAPI V3 validation YAML +is generated as a 'validation' object. + +Example: + $ operator-sdk generate openapi + $ tree pkg/apis + pkg/apis/ + └── app + └── v1alpha1 + ├── zz_generated.openapi.go + $ tree deploy/crds + ├── deploy/crds/app_v1alpha1_appservice_cr.yaml + ├── deploy/crds/app_v1alpha1_appservice_crd.yaml `, RunE: openAPIFunc, } From 35ff8252daf5c13f51da96232df15cc8d2691606 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 31 Jan 2019 10:30:10 -0800 Subject: [PATCH 33/34] override go-openapi/spec to avoid new project dep solve errors --- pkg/scaffold/cr_test.go | 6 +++--- pkg/scaffold/gopkgtoml.go | 4 ++++ pkg/scaffold/gopkgtoml_test.go | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/scaffold/cr_test.go b/pkg/scaffold/cr_test.go index 3d927f79bb..0fbbbab7d0 100644 --- a/pkg/scaffold/cr_test.go +++ b/pkg/scaffold/cr_test.go @@ -20,7 +20,7 @@ import ( "github.com/operator-framework/operator-sdk/internal/util/diffutil" ) -func TestCr(t *testing.T) { +func TestCR(t *testing.T) { r, err := NewResource(appApiVersion, appKind) if err != nil { t.Fatal(err) @@ -37,13 +37,13 @@ func TestCr(t *testing.T) { } } -func TestCrCustomSpec(t *testing.T) { +func TestCRCustomSpec(t *testing.T) { r, err := NewResource(appApiVersion, appKind) if err != nil { t.Fatal(err) } s, buf := setupScaffoldAndWriter() - err = s.Execute(appConfig, &Cr{ + err = s.Execute(appConfig, &CR{ Resource: r, Spec: "# Custom spec here\ncustomSize: 6", }) diff --git a/pkg/scaffold/gopkgtoml.go b/pkg/scaffold/gopkgtoml.go index df48646c26..f9533a88b7 100644 --- a/pkg/scaffold/gopkgtoml.go +++ b/pkg/scaffold/gopkgtoml.go @@ -63,6 +63,10 @@ required = [ name = "k8s.io/kube-openapi" revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" +[[override]] + name = "github.com/go-openapi/spec" + branch = "master" + [[override]] name = "sigs.k8s.io/controller-tools" version = "=v0.1.8" diff --git a/pkg/scaffold/gopkgtoml_test.go b/pkg/scaffold/gopkgtoml_test.go index 7a912a6416..c13c44a05e 100644 --- a/pkg/scaffold/gopkgtoml_test.go +++ b/pkg/scaffold/gopkgtoml_test.go @@ -55,6 +55,10 @@ required = [ name = "k8s.io/kube-openapi" revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" +[[override]] + name = "github.com/go-openapi/spec" + branch = "master" + [[override]] name = "sigs.k8s.io/controller-tools" version = "=v0.1.8" From 446d2f114312d534573ad0e08990e56ce9de13cc Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 31 Jan 2019 10:46:38 -0800 Subject: [PATCH 34/34] revendor --- Gopkg.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index b538288f73..4d56b8fc8c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1520,14 +1520,6 @@ revision = "12d98582e72927b6cd0123e2b4e819f9341ce62c" version = "v0.1.10" -[[projects]] - digest = "1:7719608fe0b52a4ece56c2dde37bedd95b938677d1ab0f84b8a7852e4c59f849" - name = "sigs.k8s.io/yaml" - packages = ["."] - pruneopts = "UT" - revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" - version = "v1.1.0" - [[projects]] digest = "1:682237faf8a777ed193912b47f933c5c900829ce01079da3f41455537c0e471a" name = "sigs.k8s.io/controller-tools" @@ -1543,6 +1535,14 @@ revision = "b072ef59824b16023b0e12c94d0040d99059a961" version = "v0.1.7" +[[projects]] + digest = "1:7719608fe0b52a4ece56c2dde37bedd95b938677d1ab0f84b8a7852e4c59f849" + name = "sigs.k8s.io/yaml" + packages = ["."] + pruneopts = "UT" + revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" + version = "v1.1.0" + [[projects]] branch = "master" digest = "1:9132eacc44d9bd1e03145ea2e9d4888800da7773d6edebb401f8cd34c9fb8380"