From 87859e4ccd955c44c6714648a6f757f099d318d7 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 21 May 2019 17:31:32 -0700 Subject: [PATCH 01/35] improve repo-getting behaviour --- cmd/operator-sdk/add/api.go | 2 +- cmd/operator-sdk/add/controller.go | 2 +- cmd/operator-sdk/build/cmd.go | 2 +- cmd/operator-sdk/internal/genutil/k8s.go | 2 +- cmd/operator-sdk/internal/genutil/openapi.go | 2 +- cmd/operator-sdk/migrate/cmd.go | 61 +++++++++---- cmd/operator-sdk/new/cmd.go | 90 +++++++++++++++++-- cmd/operator-sdk/olmcatalog/gen-csv.go | 2 +- cmd/operator-sdk/up/local.go | 2 +- .../scaffold/internal/deps/print_go_mod.go | 2 +- internal/util/projutil/exec.go | 31 +++++-- internal/util/projutil/project_util.go | 68 ++++++++++---- 12 files changed, 206 insertions(+), 60 deletions(-) diff --git a/cmd/operator-sdk/add/api.go b/cmd/operator-sdk/add/api.go index 2b11ba04965..68230dad976 100644 --- a/cmd/operator-sdk/add/api.go +++ b/cmd/operator-sdk/add/api.go @@ -99,7 +99,7 @@ func apiRun(cmd *cobra.Command, args []string) error { absProjectPath := projutil.MustGetwd() cfg := &input.Config{ - Repo: projutil.CheckAndGetProjectGoPkg(), + Repo: projutil.GetGoPkg(), AbsProjectPath: absProjectPath, } diff --git a/cmd/operator-sdk/add/controller.go b/cmd/operator-sdk/add/controller.go index ddb2dc2ca94..89c5258fec1 100644 --- a/cmd/operator-sdk/add/controller.go +++ b/cmd/operator-sdk/add/controller.go @@ -81,7 +81,7 @@ func controllerRun(cmd *cobra.Command, args []string) error { } cfg := &input.Config{ - Repo: projutil.CheckAndGetProjectGoPkg(), + Repo: projutil.GetGoPkg(), AbsProjectPath: projutil.MustGetwd(), } s := &scaffold.Scaffold{} diff --git a/cmd/operator-sdk/build/cmd.go b/cmd/operator-sdk/build/cmd.go index d5b334a9628..427c465d492 100644 --- a/cmd/operator-sdk/build/cmd.go +++ b/cmd/operator-sdk/build/cmd.go @@ -100,7 +100,7 @@ func buildFunc(cmd *cobra.Command, args []string) error { if projutil.IsOperatorGo() { opts := projutil.GoCmdOptions{ BinName: filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName), - PackagePath: filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir), + PackagePath: filepath.Join(projutil.GetGoPkg(), scaffold.ManagerDir), Args: goTrimFlags, Env: goBuildEnv, GoMod: projutil.IsDepManagerGoMod(), diff --git a/cmd/operator-sdk/internal/genutil/k8s.go b/cmd/operator-sdk/internal/genutil/k8s.go index 35fcf7281c3..88c566510b1 100644 --- a/cmd/operator-sdk/internal/genutil/k8s.go +++ b/cmd/operator-sdk/internal/genutil/k8s.go @@ -32,7 +32,7 @@ func K8sCodegen() error { projutil.MustInProjectRoot() wd := projutil.MustGetwd() - repoPkg := projutil.CheckAndGetProjectGoPkg() + repoPkg := projutil.GetGoPkg() srcDir := filepath.Join(wd, "vendor", "k8s.io", "code-generator") binDir := filepath.Join(wd, scaffold.BuildBinDir) diff --git a/cmd/operator-sdk/internal/genutil/openapi.go b/cmd/operator-sdk/internal/genutil/openapi.go index 13eabc17d5e..660de36eb76 100644 --- a/cmd/operator-sdk/internal/genutil/openapi.go +++ b/cmd/operator-sdk/internal/genutil/openapi.go @@ -33,7 +33,7 @@ func OpenAPIGen() error { projutil.MustInProjectRoot() absProjectPath := projutil.MustGetwd() - repoPkg := projutil.CheckAndGetProjectGoPkg() + repoPkg := projutil.GetGoPkg() srcDir := filepath.Join(absProjectPath, "vendor", "k8s.io", "kube-openapi") binDir := filepath.Join(absProjectPath, scaffold.BuildBinDir) diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 18efade6605..5f58faa2f29 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -33,6 +33,7 @@ import ( var ( depManager string headerFile string + repo string ) // NewCmd returns a command that will add source code to an existing non-go operator @@ -46,6 +47,7 @@ func NewCmd() *cobra.Command { newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's go import path. This must be set if outside of $GOPATH/src, and cannot be set of --dep-manager=dep") return newCmd } @@ -55,7 +57,13 @@ func NewCmd() *cobra.Command { func migrateRun(cmd *cobra.Command, args []string) error { projutil.MustInProjectRoot() - _ = projutil.CheckAndGetProjectGoPkg() + if err := verifyFlags(); err != nil { + return err + } + + if repo == "" { + repo = projutil.GetGoPkg() + } opType := projutil.GetOperatorType() switch opType { @@ -67,12 +75,44 @@ func migrateRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("operator of type %s cannot be migrated", opType) } +func verifyFlags() error { + // dep assumes the project's path under $GOPATH/src is the project's + // repo path. + if repo != "" && depManager == string(projutil.DepManagerDep) { + return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) + } + + inGopathSrc, err := projutil.WdInGoPathSrc() + if err != nil { + return err + } + if !inGopathSrc { + if depManager == string(projutil.DepManagerDep) { + return fmt.Errorf(`depedency manger "dep" selected but wd not in $GOPATH/src`) + } + if repo == "" && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) + } + } + + goModOn, err := projutil.GoModOn() + if err != nil { + return err + } + if !goModOn && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires wd be in $GOPATH/src` + + ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="auto" or unset`) + } + + return nil +} + // migrateAnsible runs the migration process for an ansible-based operator func migrateAnsible() error { wd := projutil.MustGetwd() cfg := &input.Config{ - Repo: projutil.CheckAndGetProjectGoPkg(), + Repo: repo, AbsProjectPath: wd, ProjectName: filepath.Base(wd), } @@ -126,7 +166,7 @@ func migrateHelm() error { wd := projutil.MustGetwd() cfg := &input.Config{ - Repo: projutil.CheckAndGetProjectGoPkg(), + Repo: repo, AbsProjectPath: wd, ProjectName: filepath.Base(wd), } @@ -180,9 +220,6 @@ func scaffoldHelmDepManager(s *scaffold.Scaffold, cfg *input.Config) error { case projutil.DepManagerDep: files = append(files, &helm.GopkgToml{}) case projutil.DepManagerGoMod: - if err := goModCheck(); err != nil { - return err - } files = append(files, &helm.GoMod{}, &scaffold.Tools{}) default: return projutil.ErrInvalidDepManager(depManager) @@ -196,21 +233,9 @@ func scaffoldAnsibleDepManager(s *scaffold.Scaffold, cfg *input.Config) error { case projutil.DepManagerDep: files = append(files, &ansible.GopkgToml{}) case projutil.DepManagerGoMod: - if err := goModCheck(); err != nil { - return err - } files = append(files, &ansible.GoMod{}, &scaffold.Tools{}) default: return projutil.ErrInvalidDepManager(depManager) } return s.Execute(cfg, files...) } - -func goModCheck() error { - goModOn, err := projutil.GoModOn() - if err == nil && !goModOn { - log.Fatal(`Dependency manager "modules" has been selected but go modules are not active. ` + - `Activate modules then run "operator-sdk migrate".`) - } - return err -} diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index b75b5792366..31edd88d616 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -55,8 +55,10 @@ generates a skeletal app-operator application in $GOPATH/src/github.com/example. newCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService) - used with \"ansible\" or \"helm\" types") newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (choices: \"go\", \"ansible\" or \"helm\")") newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's go import path. This must be set if outside of $GOPATH/src, and cannot be set of --dep-manager=dep") newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository") newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") + newCmd.Flags().BoolVar(&makeVendor, "vendor", false, "Use a vendor directory for dependencies. This flag only applies when --dep-manager=modules (the default)") newCmd.Flags().BoolVar(&generatePlaybook, "generate-playbook", false, "Generate a playbook skeleton. (Only used for --type ansible)") newCmd.Flags().StringVar(&helmChartRef, "helm-chart", "", "Initialize helm operator with existing helm chart (, /, or local path)") @@ -73,7 +75,9 @@ var ( projectName string depManager string headerFile string + repo string skipGit bool + makeVendor bool generatePlaybook bool helmChartRef string @@ -90,6 +94,10 @@ func newFunc(cmd *cobra.Command, args []string) error { return err } + if repo == "" { + repo = filepath.Join(projutil.GetGoPkg(), projectName) + } + log.Infof("Creating new %s operator '%s'.", strings.Title(operatorType), projectName) switch operatorType { @@ -109,6 +117,11 @@ func newFunc(cmd *cobra.Command, args []string) error { return err } } + + if err := checkProject(); err != nil { + return err + } + if err := initGit(); err != nil { return err } @@ -146,7 +159,7 @@ func mustBeNewProject() { func doGoScaffold() error { cfg := &input.Config{ - Repo: filepath.Join(projutil.CheckAndGetProjectGoPkg(), projectName), + Repo: repo, AbsProjectPath: filepath.Join(projutil.MustGetwd(), projectName), ProjectName: projectName, } @@ -201,6 +214,7 @@ func doGoScaffold() error { func doAnsibleScaffold() error { cfg := &input.Config{ + Repo: repo, AbsProjectPath: filepath.Join(projutil.MustGetwd(), projectName), ProjectName: projectName, } @@ -285,6 +299,7 @@ func doAnsibleScaffold() error { func doHelmScaffold() error { cfg := &input.Config{ + Repo: repo, AbsProjectPath: filepath.Join(projutil.MustGetwd(), projectName), ProjectName: projectName, } @@ -382,6 +397,35 @@ func verifyFlags() error { return fmt.Errorf("value of --api-version has wrong format (%v); format must be $GROUP_NAME/$VERSION (e.g app.example.com/v1alpha1)", apiVersion) } } + + // dep assumes the project's path under $GOPATH/src is the project's + // repo path. + if repo != "" && depManager == string(projutil.DepManagerDep) { + return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) + } + + inGopathSrc, err := projutil.WdInGoPathSrc() + if err != nil { + return err + } + if !inGopathSrc { + if depManager == string(projutil.DepManagerDep) { + return fmt.Errorf(`depedency manger "dep" selected but wd not in $GOPATH/src`) + } + if repo == "" && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) + } + } + + goModOn, err := projutil.GoModOn() + if err != nil { + return err + } + if !goModOn && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires wd be in $GOPATH/src` + + ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="auto" or unset`) + } + return nil } @@ -394,14 +438,25 @@ func execProjCmd(cmd string, args ...string) error { func getDeps() error { switch m := projutil.DepManagerType(depManager); m { case projutil.DepManagerDep: - log.Info("Running dep ensure ...") + log.Info("Running dep ensure") if err := execProjCmd("dep", "ensure", "-v"); err != nil { return err } case projutil.DepManagerGoMod: - log.Info("Running go mod ...") - if err := execProjCmd("go", "mod", "vendor", "-v"); err != nil { - return err + // Only when a user requests a vendor directory be created should "go mod" + // be run during project initialization. + if makeVendor { + log.Info("Running go mod vendor") + opts := projutil.GoCmdOptions{ + Args: []string{"-v"}, + Dir: filepath.Join(projutil.MustGetwd(), projectName), + } + if err := projutil.GoCmd("mod vendor", opts); err != nil { + return err + } + } else { + // Avoid done message. + return nil } default: return projutil.ErrInvalidDepManager(depManager) @@ -410,11 +465,34 @@ func getDeps() error { return nil } +func checkProject() error { + log.Info("Checking project") + switch projutil.DepManagerType(depManager) { + case projutil.DepManagerGoMod: + // Run "go build ./..." to make sure all packages can be built + // currectly. From "go help build": + // + // When compiling multiple packages or a single non-main package, + // build compiles the packages but discards the resulting object, + // serving only as a check that the packages can be built. + opts := projutil.GoCmdOptions{ + PackagePath: "./...", + Dir: filepath.Join(projutil.MustGetwd(), projectName), + } + if err := projutil.GoBuild(opts); err != nil { + return err + } + } + + log.Info("Check project successful.") + return nil +} + func initGit() error { if skipGit { return nil } - log.Info("Run git init ...") + log.Info("Running git init") if err := execProjCmd("git", "init"); err != nil { return err } diff --git a/cmd/operator-sdk/olmcatalog/gen-csv.go b/cmd/operator-sdk/olmcatalog/gen-csv.go index 720f60d5232..24fb69c66ce 100644 --- a/cmd/operator-sdk/olmcatalog/gen-csv.go +++ b/cmd/operator-sdk/olmcatalog/gen-csv.go @@ -77,7 +77,7 @@ func genCSVFunc(cmd *cobra.Command, args []string) error { ProjectName: filepath.Base(absProjectPath), } if projutil.IsOperatorGo() { - cfg.Repo = projutil.CheckAndGetProjectGoPkg() + cfg.Repo = projutil.GetGoPkg() } log.Infof("Generating CSV manifest version %s", csvVersion) diff --git a/cmd/operator-sdk/up/local.go b/cmd/operator-sdk/up/local.go index a81b353de80..a531790eae5 100644 --- a/cmd/operator-sdk/up/local.go +++ b/cmd/operator-sdk/up/local.go @@ -183,7 +183,7 @@ func buildLocal(outputBinName string) error { } opts := projutil.GoCmdOptions{ BinName: outputBinName, - PackagePath: filepath.Join(projutil.CheckAndGetProjectGoPkg(), scaffold.ManagerDir), + PackagePath: filepath.Join(projutil.GetGoPkg(), scaffold.ManagerDir), Args: args, GoMod: projutil.IsDepManagerGoMod(), } diff --git a/internal/pkg/scaffold/internal/deps/print_go_mod.go b/internal/pkg/scaffold/internal/deps/print_go_mod.go index be659f7b548..35bac923cdd 100644 --- a/internal/pkg/scaffold/internal/deps/print_go_mod.go +++ b/internal/pkg/scaffold/internal/deps/print_go_mod.go @@ -28,7 +28,7 @@ import ( func ExecGoModTmpl(tmpl string) ([]byte, error) { projutil.MustInProjectRoot() - repo := projutil.CheckAndGetProjectGoPkg() + repo := projutil.GetGoPkg() t, err := template.New("").Parse(tmpl) if err != nil { return nil, fmt.Errorf("failed to parse go mod template: (%v)", err) diff --git a/internal/util/projutil/exec.go b/internal/util/projutil/exec.go index 1fde21f913a..05d9513778d 100644 --- a/internal/util/projutil/exec.go +++ b/internal/util/projutil/exec.go @@ -50,7 +50,7 @@ type GoCmdOptions struct { // Dir is the dir to run "go {cmd}" in; exec.Command.Dir is set to this value. Dir string // GoMod determines whether to set the "-mod=vendor" flag. - // If true, "go {cmd}" will use modules. + // If true and ./vendor/ exists, "go {cmd}" will use vendored modules. // If false, "go {cmd}" will not use go modules. This is the default. // This applies to build, clean, get, install, list, run, and test. GoMod bool @@ -70,7 +70,7 @@ const ( // GoBuild runs "go build" configured with opts. func GoBuild(opts GoCmdOptions) error { - return goCmd(goBuildCmd, opts) + return GoCmd(goBuildCmd, opts) } // GoTest runs "go test" configured with opts. @@ -85,8 +85,8 @@ func GoTest(opts GoTestOptions) error { return ExecCmd(c) } -// goCmd runs "go cmd".. -func goCmd(cmd string, opts GoCmdOptions) error { +// GoCmd runs "go cmd".. +func GoCmd(cmd string, opts GoCmdOptions) error { bargs, err := getGeneralArgs(cmd, opts) if err != nil { return err @@ -97,7 +97,15 @@ func goCmd(cmd string, opts GoCmdOptions) error { } func getGeneralArgs(cmd string, opts GoCmdOptions) ([]string, error) { - bargs := []string{cmd} + // Go subcommands with more than one child command must be passed as + // multiple arguments instead of a spaced string, ex. "go mod init". + bargs := []string{} + for _, c := range strings.Split(cmd, " ") { + if ct := strings.TrimSpace(c); ct != "" { + bargs = append(bargs, ct) + } + } + if opts.BinName != "" { bargs = append(bargs, "-o", opts.BinName) } @@ -106,10 +114,15 @@ func getGeneralArgs(cmd string, opts GoCmdOptions) ([]string, error) { if goModOn, err := GoModOn(); err != nil { return nil, err } else if goModOn { - bargs = append(bargs, "-mod=vendor") + if info, err := os.Stat("vendor"); err == nil && info.IsDir() { + bargs = append(bargs, "-mod=vendor") + } } } - return append(bargs, opts.PackagePath), nil + if opts.PackagePath != "" { + bargs = append(bargs, opts.PackagePath) + } + return bargs, nil } func setCommandFields(c *exec.Cmd, opts GoCmdOptions) { @@ -137,14 +150,14 @@ func GoModOn() (bool, error) { if v == "on" { return true, nil } - inSrc, err := wdInGoPathSrc() + inSrc, err := WdInGoPathSrc() if err != nil { return false, err } return !inSrc && (!ok || v == "" || v == "auto"), nil } -func wdInGoPathSrc() (bool, error) { +func WdInGoPathSrc() (bool, error) { wd, err := os.Getwd() if err != nil { return false, err diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 7ba70444011..cd7d9638a1a 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -16,12 +16,14 @@ package projutil import ( "fmt" + "io/ioutil" "os" "path/filepath" "regexp" "strings" homedir "github.com/mitchellh/go-homedir" + "github.com/rogpeppe/go-internal/modfile" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -136,10 +138,48 @@ func getHomeDir() (string, error) { return homedir.Expand(hd) } -// CheckAndGetProjectGoPkg checks if this project's repository path is rooted under $GOPATH and returns the current directory's import path -// e.g: "github.com/example-inc/app-operator" -func CheckAndGetProjectGoPkg() string { - gopath := MustSetGopath(MustGetGopath()) +// GetGoPkg returns the current directory's import path by parsing it from +// wd if this project's repository path is rooted under $GOPATH/src, or +// from go.mod the project uses go modules to manage dependencies. +// +// Example: "github.com/example-inc/app-operator" +func GetGoPkg() string { + // Default to reading from go.mod, as it should usually have the (correct) + // package path, and no further processing need be done on it if so. + if _, err := os.Stat(goModFile); err == nil { + b, err := ioutil.ReadFile(goModFile) + if err != nil { + log.Fatalf("Read go.mod: %v", err) + } + mf, err := modfile.Parse(goModFile, b, nil) + if err != nil { + log.Fatalf("Parse go.mod: %v", err) + } + if mf.Module != nil && mf.Module.Mod.Path != "" { + return mf.Module.Mod.Path + } + } + + // Then try parsing package path from $GOPATH (set env or default). + goPath, ok := os.LookupEnv(GoPathEnv) + if !ok || goPath == "" { + hd, err := getHomeDir() + if err != nil { + log.Fatal(err) + } + goPath = filepath.Join(hd, "go", "src") + } else { + // MustSetFirstGopath is necessary here because the user has set GOPATH, + // which could be a path list. + goPath = MustSetFirstGopath(goPath) + } + if !strings.HasPrefix(MustGetwd(), goPath) { + log.Fatal("Could not determine project repository path: $GOPATH not set, wd in default $HOME/go/src, or wd does not contain a go.mod") + } + return parseGoPkg(goPath) +} + +func parseGoPkg(gopath string) string { goSrc := filepath.Join(gopath, SrcDir) wd := MustGetwd() currPkg := strings.Replace(wd, goSrc, "", 1) @@ -176,26 +216,16 @@ func IsOperatorHelm() bool { return err == nil && stat.IsDir() } -// MustGetGopath gets GOPATH and ensures it is set and non-empty. If GOPATH -// is not set or empty, MustGetGopath exits. -func MustGetGopath() string { - gopath, ok := os.LookupEnv(GoPathEnv) - if !ok || len(gopath) == 0 { - log.Fatal("GOPATH env not set") - } - return gopath -} - -// MustSetGopath sets GOPATH=currentGopath after processing a path list, -// if any, then returns the set path. If GOPATH cannot be set, MustSetGopath -// exits. -func MustSetGopath(currentGopath string) string { +// MustSetFirstGopath sets GOPATH to the first element of the path list in +// currentGopath, then returns the set path. +// If GOPATH cannot be set, MustSetFirstGopath exits. +func MustSetFirstGopath(currentGopath string) string { var ( newGopath string cwdInGopath bool wd = MustGetwd() ) - for _, newGopath = range strings.Split(currentGopath, ":") { + for _, newGopath = range filepath.SplitList(currentGopath) { if strings.HasPrefix(filepath.Dir(wd), newGopath) { cwdInGopath = true break From c4ef2de80458fbaa6f8319a93a97e3db44472315 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 10:04:51 -0700 Subject: [PATCH 02/35] cmd/operator-sdk/internal/genutil: use generators directly instead of external binaries --- Gopkg.toml | 4 ++ cmd/operator-sdk/internal/genutil/genutil.go | 23 ++----- cmd/operator-sdk/internal/genutil/k8s.go | 66 +++++++++++--------- cmd/operator-sdk/internal/genutil/openapi.go | 63 +++++++++++-------- 4 files changed, 84 insertions(+), 72 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index ab5fc620fcf..0145b7ca630 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -26,6 +26,10 @@ name = "k8s.io/kube-openapi" revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" +[[override]] + name = "k8s.io/code-generator" + version = "kubernetes-1.13.1" + [[constraint]] name = "sigs.k8s.io/controller-runtime" version = "=v0.1.10" diff --git a/cmd/operator-sdk/internal/genutil/genutil.go b/cmd/operator-sdk/internal/genutil/genutil.go index a30dedc92df..965c966cc73 100644 --- a/cmd/operator-sdk/internal/genutil/genutil.go +++ b/cmd/operator-sdk/internal/genutil/genutil.go @@ -20,7 +20,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" flags "github.com/operator-framework/operator-sdk/internal/pkg/flags" "github.com/operator-framework/operator-sdk/internal/pkg/scaffold" @@ -89,25 +88,15 @@ func parseGroupVersions() (map[string][]string, error) { 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{} +// createFQAPIs return a slice of all fully qualified pkg + groups + versions +// of pkg and gvs in the format "pkg/groupA/v1". +func createFQAPIs(pkg string, gvs map[string][]string) (apis []string) { 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(",") + for _, v := range vs { + apis = append(apis, filepath.Join(pkg, g, v)) } - gn++ } - return fqb.String() + return apis } func withHeaderFile(f func(string) error) (err error) { diff --git a/cmd/operator-sdk/internal/genutil/k8s.go b/cmd/operator-sdk/internal/genutil/k8s.go index 35fcf7281c3..659bde5b6a1 100644 --- a/cmd/operator-sdk/internal/genutil/k8s.go +++ b/cmd/operator-sdk/internal/genutil/k8s.go @@ -15,15 +15,19 @@ package genutil import ( + "flag" "fmt" - "os/exec" + "os" "path/filepath" "strings" "github.com/operator-framework/operator-sdk/internal/pkg/scaffold" "github.com/operator-framework/operator-sdk/internal/util/projutil" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" + generatorargs "k8s.io/code-generator/cmd/deepcopy-gen/args" + "k8s.io/gengo/examples/deepcopy-gen/generators" ) // K8sCodegen performs deepcopy code-generation for all custom resources under @@ -31,20 +35,7 @@ import ( func K8sCodegen() error { projutil.MustInProjectRoot() - wd := projutil.MustGetwd() repoPkg := projutil.CheckAndGetProjectGoPkg() - srcDir := filepath.Join(wd, "vendor", "k8s.io", "code-generator") - binDir := filepath.Join(wd, scaffold.BuildBinDir) - - genDirs := []string{ - "./cmd/client-gen", - "./cmd/lister-gen", - "./cmd/informer-gen", - "./cmd/deepcopy-gen", - } - if err := buildCodegenBinaries(genDirs, binDir, srcDir); err != nil { - return err - } gvMap, err := parseGroupVersions() if err != nil { @@ -57,8 +48,10 @@ func K8sCodegen() error { log.Infof("Running deepcopy code-generation for Custom Resource group versions: [%v]\n", gvb.String()) - fdc := func(a string) error { return deepcopyGen(binDir, repoPkg, a, gvMap) } - if err = withHeaderFile(fdc); err != nil { + apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) + fqApis := createFQAPIs(apisPkg, gvMap) + f := func(a string) error { return deepcopyGen(a, fqApis) } + if err = withHeaderFile(f); err != nil { return err } @@ -66,19 +59,36 @@ func K8sCodegen() error { return nil } -func deepcopyGen(binDir, repoPkg, hf string, gvMap map[string][]string) (err error) { - apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) - args := []string{ - "--input-dirs", createFQApis(apisPkg, gvMap), - "--output-file-base", "zz_generated.deepcopy", - "--bounding-dirs", apisPkg, - // deepcopy-gen requires a boilerplate file. Either use header or an - // empty file if header is empty. - "--go-header-file", hf, +func deepcopyGen(hf string, fqApis []string) error { + wd, err := os.Getwd() + if err != nil { + return err } - cmd := exec.Command(filepath.Join(binDir, "deepcopy-gen"), args...) - if err = projutil.ExecCmd(cmd); err != nil { - return fmt.Errorf("failed to perform deepcopy code-generation: %v", err) + flag.Set("logtostderr", "true") + for _, api := range fqApis { + apisIdx := strings.Index(api, scaffold.ApisDir) + // deepcopy-gen does not write to the target directory unless defaults + // are used for some reason. + args, cargs := generatorargs.NewDefaults() + args.InputDirs = []string{api} + args.OutputFileBaseName = "zz_generated.deepcopy" + args.OutputPackagePath = filepath.Join(wd, api[apisIdx:]) + args.GoHeaderFilePath = hf + cargs.BoundingDirs = []string{api} + args.CustomArgs = (*generators.CustomArgs)(cargs) + + if err := generatorargs.Validate(args); err != nil { + return errors.Wrap(err, "deepcopy-gen argument validation error") + } + + err := args.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + generators.Packages, + ) + if err != nil { + return errors.Wrap(err, "deepcopy-gen generator error") + } } return nil } diff --git a/cmd/operator-sdk/internal/genutil/openapi.go b/cmd/operator-sdk/internal/genutil/openapi.go index 13eabc17d5e..fd2f1d1a026 100644 --- a/cmd/operator-sdk/internal/genutil/openapi.go +++ b/cmd/operator-sdk/internal/genutil/openapi.go @@ -15,8 +15,9 @@ package genutil import ( + "flag" "fmt" - "os/exec" + "os" "path/filepath" "strings" @@ -25,7 +26,11 @@ import ( "github.com/operator-framework/operator-sdk/internal/util/k8sutil" "github.com/operator-framework/operator-sdk/internal/util/projutil" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" + gengoargs "k8s.io/gengo/args" + generatorargs "k8s.io/kube-openapi/cmd/openapi-gen/args" + "k8s.io/kube-openapi/pkg/generators" ) // OpenAPIGen generates OpenAPI validation specs for all CRD's in dirs. @@ -34,12 +39,6 @@ func OpenAPIGen() error { absProjectPath := projutil.MustGetwd() repoPkg := projutil.CheckAndGetProjectGoPkg() - srcDir := filepath.Join(absProjectPath, "vendor", "k8s.io", "kube-openapi") - binDir := filepath.Join(absProjectPath, scaffold.BuildBinDir) - - if err := buildOpenAPIGenBinary(binDir, srcDir); err != nil { - return err - } gvMap, err := parseGroupVersions() if err != nil { @@ -53,9 +52,8 @@ func OpenAPIGen() error { log.Infof("Running OpenAPI code-generation for Custom Resource group versions: [%v]\n", gvb.String()) apisPkg := filepath.Join(repoPkg, scaffold.ApisDir) - fqApiStr := createFQApis(apisPkg, gvMap) - fqApis := strings.Split(fqApiStr, ",") - f := func(a string) error { return openAPIGen(binDir, a, fqApis) } + fqApis := createFQAPIs(apisPkg, gvMap) + f := func(a string) error { return openAPIGen(a, fqApis) } if err = withHeaderFile(f); err != nil { return err } @@ -95,25 +93,36 @@ func OpenAPIGen() error { return nil } -func buildOpenAPIGenBinary(binDir, codegenSrcDir string) error { - genDirs := []string{"./cmd/openapi-gen"} - return buildCodegenBinaries(genDirs, binDir, codegenSrcDir) -} +func openAPIGen(hf string, fqApis []string) error { + wd, err := os.Getwd() + if err != nil { + return err + } + flag.Set("logtostderr", "true") + for _, api := range fqApis { + apisIdx := strings.Index(api, scaffold.ApisDir) + args := &gengoargs.GeneratorArgs{ + InputDirs: []string{api}, + OutputFileBaseName: "zz_generated.openapi", + OutputPackagePath: filepath.Join(wd, api[apisIdx:]), + GoHeaderFilePath: hf, + CustomArgs: &generatorargs.CustomArgs{ + ReportFilename: "-", // stdout + }, + } -func openAPIGen(binDir, hf string, fqApis []string) (err 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", - // openapi-gen requires a boilerplate file. Either use header or an - // empty file if header is empty. - "--go-header-file", hf, + if err := generatorargs.Validate(args); err != nil { + return errors.Wrap(err, "openapi-gen argument validation error") } - cmd := exec.Command(cgPath, args...) - if err = projutil.ExecCmd(cmd); err != nil { - return fmt.Errorf("failed to perform openapi code-generation: %v", err) + + // Generates the code for the OpenAPIDefinitions. + err := args.Execute( + generators.NameSystems(), + generators.DefaultNameSystem(), + generators.Packages, + ) + if err != nil { + return errors.Wrap(err, "openapi-gen generator error") } } return nil From e02ba685a3b64010d6a63b7e33813db6c91381d3 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 10:05:02 -0700 Subject: [PATCH 03/35] revendor --- Gopkg.lock | 23 +- vendor/k8s.io/code-generator/LICENSE | 202 ++++ .../cmd/deepcopy-gen/args/args.go | 54 + .../deepcopy-gen/generators/deepcopy.go | 924 ++++++++++++++++++ .../gengo/examples/set-gen/sets/byte.go | 203 ++++ .../k8s.io/gengo/examples/set-gen/sets/doc.go | 20 + .../gengo/examples/set-gen/sets/empty.go | 23 + .../k8s.io/gengo/examples/set-gen/sets/int.go | 203 ++++ .../gengo/examples/set-gen/sets/int64.go | 203 ++++ .../gengo/examples/set-gen/sets/string.go | 203 ++++ .../kube-openapi/cmd/openapi-gen/args/args.go | 73 ++ .../kube-openapi/pkg/generators/api_linter.go | 100 ++ .../kube-openapi/pkg/generators/extension.go | 182 ++++ .../kube-openapi/pkg/generators/openapi.go | 704 +++++++++++++ .../kube-openapi/pkg/generators/rules/doc.go | 23 + .../pkg/generators/rules/names_match.go | 172 ++++ .../kube-openapi/pkg/util/sets/empty.go | 27 + .../kube-openapi/pkg/util/sets/string.go | 207 ++++ 18 files changed, 3544 insertions(+), 2 deletions(-) create mode 100644 vendor/k8s.io/code-generator/LICENSE create mode 100644 vendor/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go create mode 100644 vendor/k8s.io/gengo/examples/deepcopy-gen/generators/deepcopy.go create mode 100644 vendor/k8s.io/gengo/examples/set-gen/sets/byte.go create mode 100644 vendor/k8s.io/gengo/examples/set-gen/sets/doc.go create mode 100644 vendor/k8s.io/gengo/examples/set-gen/sets/empty.go create mode 100644 vendor/k8s.io/gengo/examples/set-gen/sets/int.go create mode 100644 vendor/k8s.io/gengo/examples/set-gen/sets/int64.go create mode 100644 vendor/k8s.io/gengo/examples/set-gen/sets/string.go create mode 100644 vendor/k8s.io/kube-openapi/cmd/openapi-gen/args/args.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/generators/api_linter.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/generators/extension.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/generators/openapi.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/util/sets/empty.go create mode 100644 vendor/k8s.io/kube-openapi/pkg/util/sets/string.go diff --git a/Gopkg.lock b/Gopkg.lock index 6fa8dd05af4..9fcb76b8fd7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1446,10 +1446,20 @@ [[projects]] branch = "master" - digest = "1:28a32d7ac148ae6f81d5903f6455ae6530c19ba48708d0366263d61679945b13" + digest = "1:9cf5396ad425691289c57c8ec922a0ab733b9ec8afdf22044056ee708250c05f" + name = "k8s.io/code-generator" + packages = ["cmd/deepcopy-gen/args"] + pruneopts = "NUT" + revision = "639c964206c28ac3859cf36f212c24775616884a" + +[[projects]] + branch = "master" + digest = "1:6dc0e912aaeea5f98199e018d3601b4c98b7549afd0cd5ef4ca025911e0a90b2" name = "k8s.io/gengo" packages = [ "args", + "examples/deepcopy-gen/generators", + "examples/set-gen/sets", "generator", "namer", "parser", @@ -1526,12 +1536,16 @@ version = "kubernetes-1.13.1" [[projects]] - digest = "1:91a7c8838cd57527d2fa9e608e0b33226a57679ebbe1a11d568d689657337c4b" + digest = "1:6b1f11445d7962a357d4b5f98324d11d8a543f7fd368f9405eba5bdd9877e743" name = "k8s.io/kube-openapi" packages = [ + "cmd/openapi-gen/args", "pkg/common", + "pkg/generators", + "pkg/generators/rules", "pkg/util/proto", "pkg/util/proto/validation", + "pkg/util/sets", ] pruneopts = "NUT" revision = "0cf8f7e6ed1d2e3d47d02e3b6e559369af24d803" @@ -1800,6 +1814,9 @@ "k8s.io/client-go/tools/clientcmd/api", "k8s.io/client-go/transport", "k8s.io/client-go/util/workqueue", + "k8s.io/code-generator/cmd/deepcopy-gen/args", + "k8s.io/gengo/args", + "k8s.io/gengo/examples/deepcopy-gen/generators", "k8s.io/helm/pkg/chartutil", "k8s.io/helm/pkg/downloader", "k8s.io/helm/pkg/engine", @@ -1819,6 +1836,8 @@ "k8s.io/helm/pkg/tiller", "k8s.io/helm/pkg/tiller/environment", "k8s.io/klog", + "k8s.io/kube-openapi/cmd/openapi-gen/args", + "k8s.io/kube-openapi/pkg/generators", "k8s.io/kubernetes/pkg/kubectl/util", "sigs.k8s.io/controller-runtime/pkg/cache", "sigs.k8s.io/controller-runtime/pkg/client", diff --git a/vendor/k8s.io/code-generator/LICENSE b/vendor/k8s.io/code-generator/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/vendor/k8s.io/code-generator/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go b/vendor/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go new file mode 100644 index 00000000000..789713012ad --- /dev/null +++ b/vendor/k8s.io/code-generator/cmd/deepcopy-gen/args/args.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package args + +import ( + "fmt" + + "github.com/spf13/pflag" + "k8s.io/gengo/args" + "k8s.io/gengo/examples/deepcopy-gen/generators" +) + +// CustomArgs is used by the gengo framework to pass args specific to this generator. +type CustomArgs generators.CustomArgs + +// NewDefaults returns default arguments for the generator. +func NewDefaults() (*args.GeneratorArgs, *CustomArgs) { + genericArgs := args.Default().WithoutDefaultFlagParsing() + customArgs := &CustomArgs{} + genericArgs.CustomArgs = (*generators.CustomArgs)(customArgs) // convert to upstream type to make type-casts work there + genericArgs.OutputFileBaseName = "deepcopy_generated" + return genericArgs, customArgs +} + +// AddFlags add the generator flags to the flag set. +func (ca *CustomArgs) AddFlags(fs *pflag.FlagSet) { + pflag.CommandLine.StringSliceVar(&ca.BoundingDirs, "bounding-dirs", ca.BoundingDirs, + "Comma-separated list of import paths which bound the types for which deep-copies will be generated.") +} + +// Validate checks the given arguments. +func Validate(genericArgs *args.GeneratorArgs) error { + _ = genericArgs.CustomArgs.(*generators.CustomArgs) + + if len(genericArgs.OutputFileBaseName) == 0 { + return fmt.Errorf("output file base name cannot be empty") + } + + return nil +} diff --git a/vendor/k8s.io/gengo/examples/deepcopy-gen/generators/deepcopy.go b/vendor/k8s.io/gengo/examples/deepcopy-gen/generators/deepcopy.go new file mode 100644 index 00000000000..40f1306d5db --- /dev/null +++ b/vendor/k8s.io/gengo/examples/deepcopy-gen/generators/deepcopy.go @@ -0,0 +1,924 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generators + +import ( + "fmt" + "io" + "path/filepath" + "sort" + "strings" + + "k8s.io/gengo/args" + "k8s.io/gengo/examples/set-gen/sets" + "k8s.io/gengo/generator" + "k8s.io/gengo/namer" + "k8s.io/gengo/types" + + "k8s.io/klog" +) + +// CustomArgs is used tby the go2idl framework to pass args specific to this +// generator. +type CustomArgs struct { + BoundingDirs []string // Only deal with types rooted under these dirs. +} + +// This is the comment tag that carries parameters for deep-copy generation. +const ( + tagEnabledName = "k8s:deepcopy-gen" + interfacesTagName = tagEnabledName + ":interfaces" + interfacesNonPointerTagName = tagEnabledName + ":nonpointer-interfaces" // attach the DeepCopy methods to the +) + +// Known values for the comment tag. +const tagValuePackage = "package" + +// enabledTagValue holds parameters from a tagName tag. +type enabledTagValue struct { + value string + register bool +} + +func extractEnabledTypeTag(t *types.Type) *enabledTagValue { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + return extractEnabledTag(comments) +} + +func extractEnabledTag(comments []string) *enabledTagValue { + tagVals := types.ExtractCommentTags("+", comments)[tagEnabledName] + if tagVals == nil { + // No match for the tag. + return nil + } + // If there are multiple values, abort. + if len(tagVals) > 1 { + klog.Fatalf("Found %d %s tags: %q", len(tagVals), tagEnabledName, tagVals) + } + + // If we got here we are returning something. + tag := &enabledTagValue{} + + // Get the primary value. + parts := strings.Split(tagVals[0], ",") + if len(parts) >= 1 { + tag.value = parts[0] + } + + // Parse extra arguments. + parts = parts[1:] + for i := range parts { + kv := strings.SplitN(parts[i], "=", 2) + k := kv[0] + v := "" + if len(kv) == 2 { + v = kv[1] + } + switch k { + case "register": + if v != "false" { + tag.register = true + } + default: + klog.Fatalf("Unsupported %s param: %q", tagEnabledName, parts[i]) + } + } + return tag +} + +// TODO: This is created only to reduce number of changes in a single PR. +// Remove it and use PublicNamer instead. +func deepCopyNamer() *namer.NameStrategy { + return &namer.NameStrategy{ + Join: func(pre string, in []string, post string) string { + return strings.Join(in, "_") + }, + PrependPackageNames: 1, + } +} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "public": deepCopyNamer(), + "raw": namer.NewRawNamer("", nil), + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "public" +} + +func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { + boilerplate, err := arguments.LoadGoBoilerplate() + if err != nil { + klog.Fatalf("Failed loading boilerplate: %v", err) + } + + inputs := sets.NewString(context.Inputs...) + packages := generator.Packages{} + header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...) + + boundingDirs := []string{} + if customArgs, ok := arguments.CustomArgs.(*CustomArgs); ok { + if customArgs.BoundingDirs == nil { + customArgs.BoundingDirs = context.Inputs + } + for i := range customArgs.BoundingDirs { + // Strip any trailing slashes - they are not exactly "correct" but + // this is friendlier. + boundingDirs = append(boundingDirs, strings.TrimRight(customArgs.BoundingDirs[i], "/")) + } + } + + for i := range inputs { + klog.V(5).Infof("Considering pkg %q", i) + pkg := context.Universe[i] + if pkg == nil { + // If the input had no Go files, for example. + continue + } + + ptag := extractEnabledTag(pkg.Comments) + ptagValue := "" + ptagRegister := false + if ptag != nil { + ptagValue = ptag.value + if ptagValue != tagValuePackage { + klog.Fatalf("Package %v: unsupported %s value: %q", i, tagEnabledName, ptagValue) + } + ptagRegister = ptag.register + klog.V(5).Infof(" tag.value: %q, tag.register: %t", ptagValue, ptagRegister) + } else { + klog.V(5).Infof(" no tag") + } + + // If the pkg-scoped tag says to generate, we can skip scanning types. + pkgNeedsGeneration := (ptagValue == tagValuePackage) + if !pkgNeedsGeneration { + // If the pkg-scoped tag did not exist, scan all types for one that + // explicitly wants generation. + for _, t := range pkg.Types { + klog.V(5).Infof(" considering type %q", t.Name.String()) + ttag := extractEnabledTypeTag(t) + if ttag != nil && ttag.value == "true" { + klog.V(5).Infof(" tag=true") + if !copyableType(t) { + klog.Fatalf("Type %v requests deepcopy generation but is not copyable", t) + } + pkgNeedsGeneration = true + break + } + } + } + + if pkgNeedsGeneration { + klog.V(3).Infof("Package %q needs generation", i) + path := pkg.Path + // if the source path is within a /vendor/ directory (for example, + // k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1), allow + // generation to output to the proper relative path (under vendor). + // Otherwise, the generator will create the file in the wrong location + // in the output directory. + // TODO: build a more fundamental concept in gengo for dealing with modifications + // to vendored packages. + if strings.HasPrefix(pkg.SourcePath, arguments.OutputBase) { + expandedPath := strings.TrimPrefix(pkg.SourcePath, arguments.OutputBase) + if strings.Contains(expandedPath, "/vendor/") { + path = expandedPath + } + } + packages = append(packages, + &generator.DefaultPackage{ + PackageName: strings.Split(filepath.Base(pkg.Path), ".")[0], + PackagePath: path, + HeaderText: header, + GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{ + NewGenDeepCopy(arguments.OutputFileBaseName, pkg.Path, boundingDirs, (ptagValue == tagValuePackage), ptagRegister), + } + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + return t.Name.Package == pkg.Path + }, + }) + } + } + return packages +} + +// genDeepCopy produces a file with autogenerated deep-copy functions. +type genDeepCopy struct { + generator.DefaultGen + targetPackage string + boundingDirs []string + allTypes bool + registerTypes bool + imports namer.ImportTracker + typesForInit []*types.Type +} + +func NewGenDeepCopy(sanitizedName, targetPackage string, boundingDirs []string, allTypes, registerTypes bool) generator.Generator { + return &genDeepCopy{ + DefaultGen: generator.DefaultGen{ + OptionalName: sanitizedName, + }, + targetPackage: targetPackage, + boundingDirs: boundingDirs, + allTypes: allTypes, + registerTypes: registerTypes, + imports: generator.NewImportTracker(), + typesForInit: make([]*types.Type, 0), + } +} + +func (g *genDeepCopy) Namers(c *generator.Context) namer.NameSystems { + // Have the raw namer for this file track what it imports. + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.targetPackage, g.imports), + } +} + +func (g *genDeepCopy) Filter(c *generator.Context, t *types.Type) bool { + // Filter out types not being processed or not copyable within the package. + enabled := g.allTypes + if !enabled { + ttag := extractEnabledTypeTag(t) + if ttag != nil && ttag.value == "true" { + enabled = true + } + } + if !enabled { + return false + } + if !copyableType(t) { + klog.V(2).Infof("Type %v is not copyable", t) + return false + } + klog.V(4).Infof("Type %v is copyable", t) + g.typesForInit = append(g.typesForInit, t) + return true +} + +func (g *genDeepCopy) copyableAndInBounds(t *types.Type) bool { + if !copyableType(t) { + return false + } + // Only packages within the restricted range can be processed. + if !isRootedUnder(t.Name.Package, g.boundingDirs) { + return false + } + return true +} + +// deepCopyMethod returns the signature of a DeepCopy() method, nil or an error +// if the type does not match. This allows more efficient deep copy +// implementations to be defined by the type's author. The correct signature +// for a type T is: +// func (t T) DeepCopy() T +// or: +// func (t *T) DeepCopy() *T +func deepCopyMethod(t *types.Type) (*types.Signature, error) { + f, found := t.Methods["DeepCopy"] + if !found { + return nil, nil + } + if len(f.Signature.Parameters) != 0 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected no parameters", t) + } + if len(f.Signature.Results) != 1 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected exactly one result", t) + } + + ptrResult := f.Signature.Results[0].Kind == types.Pointer && f.Signature.Results[0].Elem.Name == t.Name + nonPtrResult := f.Signature.Results[0].Name == t.Name + + if !ptrResult && !nonPtrResult { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected to return %s or *%s", t, t.Name.Name, t.Name.Name) + } + + ptrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Kind == types.Pointer && f.Signature.Receiver.Elem.Name == t.Name + nonPtrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Name == t.Name + + if ptrRcvr && !ptrResult { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected a *%s result for a *%s receiver", t, t.Name.Name, t.Name.Name) + } + if nonPtrRcvr && !nonPtrResult { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected a %s result for a %s receiver", t, t.Name.Name, t.Name.Name) + } + + return f.Signature, nil +} + +// deepCopyMethodOrDie returns the signatrue of a DeepCopy method, nil or calls klog.Fatalf +// if the type does not match. +func deepCopyMethodOrDie(t *types.Type) *types.Signature { + ret, err := deepCopyMethod(t) + if err != nil { + klog.Fatal(err) + } + return ret +} + +// deepCopyIntoMethod returns the signature of a DeepCopyInto() method, nil or an error +// if the type is wrong. DeepCopyInto allows more efficient deep copy +// implementations to be defined by the type's author. The correct signature +// for a type T is: +// func (t T) DeepCopyInto(t *T) +// or: +// func (t *T) DeepCopyInto(t *T) +func deepCopyIntoMethod(t *types.Type) (*types.Signature, error) { + f, found := t.Methods["DeepCopyInto"] + if !found { + return nil, nil + } + if len(f.Signature.Parameters) != 1 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected exactly one parameter", t) + } + if len(f.Signature.Results) != 0 { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected no result type", t) + } + + ptrParam := f.Signature.Parameters[0].Kind == types.Pointer && f.Signature.Parameters[0].Elem.Name == t.Name + + if !ptrParam { + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected parameter of type *%s", t, t.Name.Name) + } + + ptrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Kind == types.Pointer && f.Signature.Receiver.Elem.Name == t.Name + nonPtrRcvr := f.Signature.Receiver != nil && f.Signature.Receiver.Name == t.Name + + if !ptrRcvr && !nonPtrRcvr { + // this should never happen + return nil, fmt.Errorf("type %v: invalid DeepCopy signature, expected a receiver of type %s or *%s", t, t.Name.Name, t.Name.Name) + } + + return f.Signature, nil +} + +// deepCopyIntoMethodOrDie returns the signature of a DeepCopyInto() method, nil or calls klog.Fatalf +// if the type is wrong. +func deepCopyIntoMethodOrDie(t *types.Type) *types.Signature { + ret, err := deepCopyIntoMethod(t) + if err != nil { + klog.Fatal(err) + } + return ret +} + +func isRootedUnder(pkg string, roots []string) bool { + // Add trailing / to avoid false matches, e.g. foo/bar vs foo/barn. This + // assumes that bounding dirs do not have trailing slashes. + pkg = pkg + "/" + for _, root := range roots { + if strings.HasPrefix(pkg, root+"/") { + return true + } + } + return false +} + +func copyableType(t *types.Type) bool { + // If the type opts out of copy-generation, stop. + ttag := extractEnabledTypeTag(t) + if ttag != nil && ttag.value == "false" { + return false + } + + // Filter out private types. + if namer.IsPrivateGoName(t.Name.Name) { + return false + } + + if t.Kind == types.Alias { + // if the underlying built-in is not deepcopy-able, deepcopy is opt-in through definition of custom methods. + // Note that aliases of builtins, maps, slices can have deepcopy methods. + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + return true + } else { + return t.Underlying.Kind != types.Builtin || copyableType(t.Underlying) + } + } + + if t.Kind != types.Struct { + return false + } + + return true +} + +func underlyingType(t *types.Type) *types.Type { + for t.Kind == types.Alias { + t = t.Underlying + } + return t +} + +func (g *genDeepCopy) isOtherPackage(pkg string) bool { + if pkg == g.targetPackage { + return false + } + if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") { + return false + } + return true +} + +func (g *genDeepCopy) Imports(c *generator.Context) (imports []string) { + importLines := []string{} + for _, singleImport := range g.imports.ImportLines() { + if g.isOtherPackage(singleImport) { + importLines = append(importLines, singleImport) + } + } + return importLines +} + +func argsFromType(ts ...*types.Type) generator.Args { + a := generator.Args{ + "type": ts[0], + } + for i, t := range ts { + a[fmt.Sprintf("type%d", i+1)] = t + } + return a +} + +func (g *genDeepCopy) Init(c *generator.Context, w io.Writer) error { + return nil +} + +func (g *genDeepCopy) needsGeneration(t *types.Type) bool { + tag := extractEnabledTypeTag(t) + tv := "" + if tag != nil { + tv = tag.value + if tv != "true" && tv != "false" { + klog.Fatalf("Type %v: unsupported %s value: %q", t, tagEnabledName, tag.value) + } + } + if g.allTypes && tv == "false" { + // The whole package is being generated, but this type has opted out. + klog.V(5).Infof("Not generating for type %v because type opted out", t) + return false + } + if !g.allTypes && tv != "true" { + // The whole package is NOT being generated, and this type has NOT opted in. + klog.V(5).Infof("Not generating for type %v because type did not opt in", t) + return false + } + return true +} + +func extractInterfacesTag(t *types.Type) []string { + var result []string + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + values := types.ExtractCommentTags("+", comments)[interfacesTagName] + for _, v := range values { + if len(v) == 0 { + continue + } + intfs := strings.Split(v, ",") + for _, intf := range intfs { + if intf == "" { + continue + } + result = append(result, intf) + } + } + return result +} + +func extractNonPointerInterfaces(t *types.Type) (bool, error) { + comments := append(append([]string{}, t.SecondClosestCommentLines...), t.CommentLines...) + values := types.ExtractCommentTags("+", comments)[interfacesNonPointerTagName] + if len(values) == 0 { + return false, nil + } + result := values[0] == "true" + for _, v := range values { + if v == "true" != result { + return false, fmt.Errorf("contradicting %v value %q found to previous value %v", interfacesNonPointerTagName, v, result) + } + } + return result, nil +} + +func (g *genDeepCopy) deepCopyableInterfacesInner(c *generator.Context, t *types.Type) ([]*types.Type, error) { + if t.Kind != types.Struct { + return nil, nil + } + + intfs := extractInterfacesTag(t) + + var ts []*types.Type + for _, intf := range intfs { + t := types.ParseFullyQualifiedName(intf) + c.AddDir(t.Package) + intfT := c.Universe.Type(t) + if intfT == nil { + return nil, fmt.Errorf("unknown type %q in %s tag of type %s", intf, interfacesTagName, intfT) + } + if intfT.Kind != types.Interface { + return nil, fmt.Errorf("type %q in %s tag of type %s is not an interface, but: %q", intf, interfacesTagName, t, intfT.Kind) + } + g.imports.AddType(intfT) + ts = append(ts, intfT) + } + + return ts, nil +} + +// deepCopyableInterfaces returns the interface types to implement and whether they apply to a non-pointer receiver. +func (g *genDeepCopy) deepCopyableInterfaces(c *generator.Context, t *types.Type) ([]*types.Type, bool, error) { + ts, err := g.deepCopyableInterfacesInner(c, t) + if err != nil { + return nil, false, err + } + + set := map[string]*types.Type{} + for _, t := range ts { + set[t.String()] = t + } + + result := []*types.Type{} + for _, t := range set { + result = append(result, t) + } + + TypeSlice(result).Sort() // we need a stable sorting because it determines the order in generation + + nonPointerReceiver, err := extractNonPointerInterfaces(t) + if err != nil { + return nil, false, err + } + + return result, nonPointerReceiver, nil +} + +type TypeSlice []*types.Type + +func (s TypeSlice) Len() int { return len(s) } +func (s TypeSlice) Less(i, j int) bool { return s[i].String() < s[j].String() } +func (s TypeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s TypeSlice) Sort() { sort.Sort(s) } + +func (g *genDeepCopy) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + if !g.needsGeneration(t) { + return nil + } + klog.V(5).Infof("Generating deepcopy function for type %v", t) + + sw := generator.NewSnippetWriter(w, c, "$", "$") + args := argsFromType(t) + + if deepCopyIntoMethodOrDie(t) == nil { + sw.Do("// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\n", args) + if isReference(t) { + sw.Do("func (in $.type|raw$) DeepCopyInto(out *$.type|raw$) {\n", args) + sw.Do("{in:=&in\n", nil) + } else { + sw.Do("func (in *$.type|raw$) DeepCopyInto(out *$.type|raw$) {\n", args) + } + if deepCopyMethodOrDie(t) != nil { + if t.Methods["DeepCopy"].Signature.Receiver.Kind == types.Pointer { + sw.Do("clone := in.DeepCopy()\n", nil) + sw.Do("*out = *clone\n", nil) + } else { + sw.Do("*out = in.DeepCopy()\n", nil) + } + sw.Do("return\n", nil) + } else { + g.generateFor(t, sw) + sw.Do("return\n", nil) + } + if isReference(t) { + sw.Do("}\n", nil) + } + sw.Do("}\n\n", nil) + } + + if deepCopyMethodOrDie(t) == nil { + sw.Do("// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new $.type|raw$.\n", args) + if isReference(t) { + sw.Do("func (in $.type|raw$) DeepCopy() $.type|raw$ {\n", args) + } else { + sw.Do("func (in *$.type|raw$) DeepCopy() *$.type|raw$ {\n", args) + } + sw.Do("if in == nil { return nil }\n", nil) + sw.Do("out := new($.type|raw$)\n", args) + sw.Do("in.DeepCopyInto(out)\n", nil) + if isReference(t) { + sw.Do("return *out\n", nil) + } else { + sw.Do("return out\n", nil) + } + sw.Do("}\n\n", nil) + } + + intfs, nonPointerReceiver, err := g.deepCopyableInterfaces(c, t) + if err != nil { + return err + } + for _, intf := range intfs { + sw.Do(fmt.Sprintf("// DeepCopy%s is an autogenerated deepcopy function, copying the receiver, creating a new $.type2|raw$.\n", intf.Name.Name), argsFromType(t, intf)) + if nonPointerReceiver { + sw.Do(fmt.Sprintf("func (in $.type|raw$) DeepCopy%s() $.type2|raw$ {\n", intf.Name.Name), argsFromType(t, intf)) + sw.Do("return *in.DeepCopy()", nil) + sw.Do("}\n\n", nil) + } else { + sw.Do(fmt.Sprintf("func (in *$.type|raw$) DeepCopy%s() $.type2|raw$ {\n", intf.Name.Name), argsFromType(t, intf)) + sw.Do("if c := in.DeepCopy(); c != nil {\n", nil) + sw.Do("return c\n", nil) + sw.Do("}\n", nil) + sw.Do("return nil\n", nil) + sw.Do("}\n\n", nil) + } + } + + return sw.Error() +} + +// isReference return true for pointer, maps, slices and aliases of those. +func isReference(t *types.Type) bool { + if t.Kind == types.Pointer || t.Kind == types.Map || t.Kind == types.Slice { + return true + } + return t.Kind == types.Alias && isReference(underlyingType(t)) +} + +// we use the system of shadowing 'in' and 'out' so that the same code is valid +// at any nesting level. This makes the autogenerator easy to understand, and +// the compiler shouldn't care. +func (g *genDeepCopy) generateFor(t *types.Type, sw *generator.SnippetWriter) { + // derive inner types if t is an alias. We call the do* methods below with the alias type. + // basic rule: generate according to inner type, but construct objects with the alias type. + ut := underlyingType(t) + + var f func(*types.Type, *generator.SnippetWriter) + switch ut.Kind { + case types.Builtin: + f = g.doBuiltin + case types.Map: + f = g.doMap + case types.Slice: + f = g.doSlice + case types.Struct: + f = g.doStruct + case types.Pointer: + f = g.doPointer + case types.Interface: + // interfaces are handled in-line in the other cases + klog.Fatalf("Hit an interface type %v. This should never happen.", t) + case types.Alias: + // can never happen because we branch on the underlying type which is never an alias + klog.Fatalf("Hit an alias type %v. This should never happen.", t) + default: + klog.Fatalf("Hit an unsupported type %v.", t) + } + f(t, sw) +} + +// doBuiltin generates code for a builtin or an alias to a builtin. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doBuiltin(t *types.Type, sw *generator.SnippetWriter) { + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + sw.Do("*out = *in\n", nil) +} + +// doMap generates code for a map or an alias to a map. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doMap(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + uet := underlyingType(ut.Elem) + + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + if !ut.Key.IsAssignable() { + klog.Fatalf("Hit an unsupported type %v for: %v", uet, t) + } + + sw.Do("*out = make($.|raw$, len(*in))\n", t) + sw.Do("for key, val := range *in {\n", nil) + dc, dci := deepCopyMethodOrDie(ut.Elem), deepCopyIntoMethodOrDie(ut.Elem) + switch { + case dc != nil || dci != nil: + // Note: a DeepCopy exists because it is added if DeepCopyInto is manually defined + leftPointer := ut.Elem.Kind == types.Pointer + rightPointer := !isReference(ut.Elem) + if dc != nil { + rightPointer = dc.Results[0].Kind == types.Pointer + } + if leftPointer == rightPointer { + sw.Do("(*out)[key] = val.DeepCopy()\n", nil) + } else if leftPointer { + sw.Do("x := val.DeepCopy()\n", nil) + sw.Do("(*out)[key] = &x\n", nil) + } else { + sw.Do("(*out)[key] = *val.DeepCopy()\n", nil) + } + case ut.Elem.IsAnonymousStruct(): // not uet here because it needs type cast + sw.Do("(*out)[key] = val\n", nil) + case uet.IsAssignable(): + sw.Do("(*out)[key] = val\n", nil) + case uet.Kind == types.Interface: + // Note: do not generate code that won't compile as `DeepCopyinterface{}()` is not a valid function + if uet.Name.Name == "interface{}" { + klog.Fatalf("DeepCopy of %q is unsupported. Instead, use named interfaces with DeepCopy as one of the methods.", uet.Name.Name) + } + sw.Do("if val == nil {(*out)[key]=nil} else {\n", nil) + // Note: if t.Elem has been an alias "J" of an interface "I" in Go, we will see it + // as kind Interface of name "J" here, i.e. generate val.DeepCopyJ(). The golang + // parser does not give us the underlying interface name. So we cannot do any better. + sw.Do(fmt.Sprintf("(*out)[key] = val.DeepCopy%s()\n", uet.Name.Name), nil) + sw.Do("}\n", nil) + case uet.Kind == types.Slice || uet.Kind == types.Map || uet.Kind == types.Pointer: + sw.Do("var outVal $.|raw$\n", uet) + sw.Do("if val == nil { (*out)[key] = nil } else {\n", nil) + sw.Do("in, out := &val, &outVal\n", uet) + g.generateFor(ut.Elem, sw) + sw.Do("}\n", nil) + sw.Do("(*out)[key] = outVal\n", nil) + case uet.Kind == types.Struct: + sw.Do("(*out)[key] = *val.DeepCopy()\n", uet) + default: + klog.Fatalf("Hit an unsupported type %v for %v", uet, t) + } + sw.Do("}\n", nil) +} + +// doSlice generates code for a slice or an alias to a slice. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doSlice(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + uet := underlyingType(ut.Elem) + + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + sw.Do("*out = make($.|raw$, len(*in))\n", t) + if deepCopyMethodOrDie(ut.Elem) != nil || deepCopyIntoMethodOrDie(ut.Elem) != nil { + sw.Do("for i := range *in {\n", nil) + // Note: a DeepCopyInto exists because it is added if DeepCopy is manually defined + sw.Do("(*in)[i].DeepCopyInto(&(*out)[i])\n", nil) + sw.Do("}\n", nil) + } else if uet.Kind == types.Builtin || uet.IsAssignable() { + sw.Do("copy(*out, *in)\n", nil) + } else { + sw.Do("for i := range *in {\n", nil) + if uet.Kind == types.Slice || uet.Kind == types.Map || uet.Kind == types.Pointer || deepCopyMethodOrDie(ut.Elem) != nil || deepCopyIntoMethodOrDie(ut.Elem) != nil { + sw.Do("if (*in)[i] != nil {\n", nil) + sw.Do("in, out := &(*in)[i], &(*out)[i]\n", nil) + g.generateFor(ut.Elem, sw) + sw.Do("}\n", nil) + } else if uet.Kind == types.Interface { + // Note: do not generate code that won't compile as `DeepCopyinterface{}()` is not a valid function + if uet.Name.Name == "interface{}" { + klog.Fatalf("DeepCopy of %q is unsupported. Instead, use named interfaces with DeepCopy as one of the methods.", uet.Name.Name) + } + sw.Do("if (*in)[i] != nil {\n", nil) + // Note: if t.Elem has been an alias "J" of an interface "I" in Go, we will see it + // as kind Interface of name "J" here, i.e. generate val.DeepCopyJ(). The golang + // parser does not give us the underlying interface name. So we cannot do any better. + sw.Do(fmt.Sprintf("(*out)[i] = (*in)[i].DeepCopy%s()\n", uet.Name.Name), nil) + sw.Do("}\n", nil) + } else if uet.Kind == types.Struct { + sw.Do("(*in)[i].DeepCopyInto(&(*out)[i])\n", nil) + } else { + klog.Fatalf("Hit an unsupported type %v for %v", uet, t) + } + sw.Do("}\n", nil) + } +} + +// doStruct generates code for a struct or an alias to a struct. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doStruct(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + + if deepCopyMethodOrDie(t) != nil || deepCopyIntoMethodOrDie(t) != nil { + sw.Do("*out = in.DeepCopy()\n", nil) + return + } + + // Simple copy covers a lot of cases. + sw.Do("*out = *in\n", nil) + + // Now fix-up fields as needed. + for _, m := range ut.Members { + ft := m.Type + uft := underlyingType(ft) + + args := generator.Args{ + "type": ft, + "kind": ft.Kind, + "name": m.Name, + } + dc, dci := deepCopyMethodOrDie(ft), deepCopyIntoMethodOrDie(ft) + switch { + case dc != nil || dci != nil: + // Note: a DeepCopyInto exists because it is added if DeepCopy is manually defined + leftPointer := ft.Kind == types.Pointer + rightPointer := !isReference(ft) + if dc != nil { + rightPointer = dc.Results[0].Kind == types.Pointer + } + if leftPointer == rightPointer { + sw.Do("out.$.name$ = in.$.name$.DeepCopy()\n", args) + } else if leftPointer { + sw.Do("x := in.$.name$.DeepCopy()\n", args) + sw.Do("out.$.name$ = = &x\n", args) + } else { + sw.Do("in.$.name$.DeepCopyInto(&out.$.name$)\n", args) + } + case uft.Kind == types.Builtin: + // the initial *out = *in was enough + case uft.Kind == types.Map, uft.Kind == types.Slice, uft.Kind == types.Pointer: + // Fixup non-nil reference-semantic types. + sw.Do("if in.$.name$ != nil {\n", args) + sw.Do("in, out := &in.$.name$, &out.$.name$\n", args) + g.generateFor(ft, sw) + sw.Do("}\n", nil) + case uft.Kind == types.Struct: + if ft.IsAssignable() { + sw.Do("out.$.name$ = in.$.name$\n", args) + } else { + sw.Do("in.$.name$.DeepCopyInto(&out.$.name$)\n", args) + } + case uft.Kind == types.Interface: + // Note: do not generate code that won't compile as `DeepCopyinterface{}()` is not a valid function + if uft.Name.Name == "interface{}" { + klog.Fatalf("DeepCopy of %q is unsupported. Instead, use named interfaces with DeepCopy as one of the methods.", uft.Name.Name) + } + sw.Do("if in.$.name$ != nil {\n", args) + // Note: if t.Elem has been an alias "J" of an interface "I" in Go, we will see it + // as kind Interface of name "J" here, i.e. generate val.DeepCopyJ(). The golang + // parser does not give us the underlying interface name. So we cannot do any better. + sw.Do(fmt.Sprintf("out.$.name$ = in.$.name$.DeepCopy%s()\n", uft.Name.Name), args) + sw.Do("}\n", nil) + default: + klog.Fatalf("Hit an unsupported type %v for %v, from %v", uft, ft, t) + } + } +} + +// doPointer generates code for a pointer or an alias to a pointer. The generated code is +// is the same for both cases, i.e. it's the code for the underlying type. +func (g *genDeepCopy) doPointer(t *types.Type, sw *generator.SnippetWriter) { + ut := underlyingType(t) + uet := underlyingType(ut.Elem) + + dc, dci := deepCopyMethodOrDie(ut.Elem), deepCopyIntoMethodOrDie(ut.Elem) + switch { + case dc != nil || dci != nil: + rightPointer := !isReference(ut.Elem) + if dc != nil { + rightPointer = dc.Results[0].Kind == types.Pointer + } + if rightPointer { + sw.Do("*out = (*in).DeepCopy()\n", nil) + } else { + sw.Do("x := (*in).DeepCopy()\n", nil) + sw.Do("*out = &x\n", nil) + } + case uet.IsAssignable(): + sw.Do("*out = new($.Elem|raw$)\n", ut) + sw.Do("**out = **in", nil) + case uet.Kind == types.Map, uet.Kind == types.Slice, uet.Kind == types.Pointer: + sw.Do("*out = new($.Elem|raw$)\n", ut) + sw.Do("if **in != nil {\n", nil) + sw.Do("in, out := *in, *out\n", nil) + g.generateFor(uet, sw) + sw.Do("}\n", nil) + case uet.Kind == types.Struct: + sw.Do("*out = new($.Elem|raw$)\n", ut) + sw.Do("(*in).DeepCopyInto(*out)\n", nil) + default: + klog.Fatalf("Hit an unsupported type %v for %v", uet, t) + } +} diff --git a/vendor/k8s.io/gengo/examples/set-gen/sets/byte.go b/vendor/k8s.io/gengo/examples/set-gen/sets/byte.go new file mode 100644 index 00000000000..766f4501e0f --- /dev/null +++ b/vendor/k8s.io/gengo/examples/set-gen/sets/byte.go @@ -0,0 +1,203 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +package sets + +import ( + "reflect" + "sort" +) + +// sets.Byte is a set of bytes, implemented via map[byte]struct{} for minimal memory consumption. +type Byte map[byte]Empty + +// NewByte creates a Byte from a list of values. +func NewByte(items ...byte) Byte { + ss := Byte{} + ss.Insert(items...) + return ss +} + +// ByteKeySet creates a Byte from a keys of a map[byte](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func ByteKeySet(theMap interface{}) Byte { + v := reflect.ValueOf(theMap) + ret := Byte{} + + for _, keyValue := range v.MapKeys() { + ret.Insert(keyValue.Interface().(byte)) + } + return ret +} + +// Insert adds items to the set. +func (s Byte) Insert(items ...byte) { + for _, item := range items { + s[item] = Empty{} + } +} + +// Delete removes all items from the set. +func (s Byte) Delete(items ...byte) { + for _, item := range items { + delete(s, item) + } +} + +// Has returns true if and only if item is contained in the set. +func (s Byte) Has(item byte) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Byte) HasAll(items ...byte) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s Byte) HasAny(items ...byte) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s Byte) Difference(s2 Byte) Byte { + result := NewByte() + for key := range s { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Byte) Union(s2 Byte) Byte { + result := NewByte() + for key := range s1 { + result.Insert(key) + } + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Byte) Intersection(s2 Byte) Byte { + var walk, other Byte + result := NewByte() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Byte) IsSuperset(s2 Byte) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Byte) Equal(s2 Byte) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +type sortableSliceOfByte []byte + +func (s sortableSliceOfByte) Len() int { return len(s) } +func (s sortableSliceOfByte) Less(i, j int) bool { return lessByte(s[i], s[j]) } +func (s sortableSliceOfByte) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// List returns the contents as a sorted byte slice. +func (s Byte) List() []byte { + res := make(sortableSliceOfByte, 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return []byte(res) +} + +// UnsortedList returns the slice with contents in random order. +func (s Byte) UnsortedList() []byte { + res := make([]byte, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// Returns a single element from the set. +func (s Byte) PopAny() (byte, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue byte + return zeroValue, false +} + +// Len returns the size of the set. +func (s Byte) Len() int { + return len(s) +} + +func lessByte(lhs, rhs byte) bool { + return lhs < rhs +} diff --git a/vendor/k8s.io/gengo/examples/set-gen/sets/doc.go b/vendor/k8s.io/gengo/examples/set-gen/sets/doc.go new file mode 100644 index 00000000000..b152a0bf00f --- /dev/null +++ b/vendor/k8s.io/gengo/examples/set-gen/sets/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +// Package sets has auto-generated set types. +package sets diff --git a/vendor/k8s.io/gengo/examples/set-gen/sets/empty.go b/vendor/k8s.io/gengo/examples/set-gen/sets/empty.go new file mode 100644 index 00000000000..e11e622c5ba --- /dev/null +++ b/vendor/k8s.io/gengo/examples/set-gen/sets/empty.go @@ -0,0 +1,23 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +package sets + +// Empty is public since it is used by some internal API objects for conversions between external +// string arrays and internal sets, and conversion logic requires public types today. +type Empty struct{} diff --git a/vendor/k8s.io/gengo/examples/set-gen/sets/int.go b/vendor/k8s.io/gengo/examples/set-gen/sets/int.go new file mode 100644 index 00000000000..a0a513cd9b5 --- /dev/null +++ b/vendor/k8s.io/gengo/examples/set-gen/sets/int.go @@ -0,0 +1,203 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +package sets + +import ( + "reflect" + "sort" +) + +// sets.Int is a set of ints, implemented via map[int]struct{} for minimal memory consumption. +type Int map[int]Empty + +// NewInt creates a Int from a list of values. +func NewInt(items ...int) Int { + ss := Int{} + ss.Insert(items...) + return ss +} + +// IntKeySet creates a Int from a keys of a map[int](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func IntKeySet(theMap interface{}) Int { + v := reflect.ValueOf(theMap) + ret := Int{} + + for _, keyValue := range v.MapKeys() { + ret.Insert(keyValue.Interface().(int)) + } + return ret +} + +// Insert adds items to the set. +func (s Int) Insert(items ...int) { + for _, item := range items { + s[item] = Empty{} + } +} + +// Delete removes all items from the set. +func (s Int) Delete(items ...int) { + for _, item := range items { + delete(s, item) + } +} + +// Has returns true if and only if item is contained in the set. +func (s Int) Has(item int) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int) HasAll(items ...int) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s Int) HasAny(items ...int) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s Int) Difference(s2 Int) Int { + result := NewInt() + for key := range s { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int) Union(s2 Int) Int { + result := NewInt() + for key := range s1 { + result.Insert(key) + } + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int) Intersection(s2 Int) Int { + var walk, other Int + result := NewInt() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int) IsSuperset(s2 Int) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int) Equal(s2 Int) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +type sortableSliceOfInt []int + +func (s sortableSliceOfInt) Len() int { return len(s) } +func (s sortableSliceOfInt) Less(i, j int) bool { return lessInt(s[i], s[j]) } +func (s sortableSliceOfInt) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// List returns the contents as a sorted int slice. +func (s Int) List() []int { + res := make(sortableSliceOfInt, 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return []int(res) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int) UnsortedList() []int { + res := make([]int, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// Returns a single element from the set. +func (s Int) PopAny() (int, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue int + return zeroValue, false +} + +// Len returns the size of the set. +func (s Int) Len() int { + return len(s) +} + +func lessInt(lhs, rhs int) bool { + return lhs < rhs +} diff --git a/vendor/k8s.io/gengo/examples/set-gen/sets/int64.go b/vendor/k8s.io/gengo/examples/set-gen/sets/int64.go new file mode 100644 index 00000000000..9ca9af0c591 --- /dev/null +++ b/vendor/k8s.io/gengo/examples/set-gen/sets/int64.go @@ -0,0 +1,203 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +package sets + +import ( + "reflect" + "sort" +) + +// sets.Int64 is a set of int64s, implemented via map[int64]struct{} for minimal memory consumption. +type Int64 map[int64]Empty + +// NewInt64 creates a Int64 from a list of values. +func NewInt64(items ...int64) Int64 { + ss := Int64{} + ss.Insert(items...) + return ss +} + +// Int64KeySet creates a Int64 from a keys of a map[int64](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func Int64KeySet(theMap interface{}) Int64 { + v := reflect.ValueOf(theMap) + ret := Int64{} + + for _, keyValue := range v.MapKeys() { + ret.Insert(keyValue.Interface().(int64)) + } + return ret +} + +// Insert adds items to the set. +func (s Int64) Insert(items ...int64) { + for _, item := range items { + s[item] = Empty{} + } +} + +// Delete removes all items from the set. +func (s Int64) Delete(items ...int64) { + for _, item := range items { + delete(s, item) + } +} + +// Has returns true if and only if item is contained in the set. +func (s Int64) Has(item int64) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s Int64) HasAll(items ...int64) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s Int64) HasAny(items ...int64) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s Int64) Difference(s2 Int64) Int64 { + result := NewInt64() + for key := range s { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 Int64) Union(s2 Int64) Int64 { + result := NewInt64() + for key := range s1 { + result.Insert(key) + } + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 Int64) Intersection(s2 Int64) Int64 { + var walk, other Int64 + result := NewInt64() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 Int64) IsSuperset(s2 Int64) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 Int64) Equal(s2 Int64) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +type sortableSliceOfInt64 []int64 + +func (s sortableSliceOfInt64) Len() int { return len(s) } +func (s sortableSliceOfInt64) Less(i, j int) bool { return lessInt64(s[i], s[j]) } +func (s sortableSliceOfInt64) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// List returns the contents as a sorted int64 slice. +func (s Int64) List() []int64 { + res := make(sortableSliceOfInt64, 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return []int64(res) +} + +// UnsortedList returns the slice with contents in random order. +func (s Int64) UnsortedList() []int64 { + res := make([]int64, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// Returns a single element from the set. +func (s Int64) PopAny() (int64, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue int64 + return zeroValue, false +} + +// Len returns the size of the set. +func (s Int64) Len() int { + return len(s) +} + +func lessInt64(lhs, rhs int64) bool { + return lhs < rhs +} diff --git a/vendor/k8s.io/gengo/examples/set-gen/sets/string.go b/vendor/k8s.io/gengo/examples/set-gen/sets/string.go new file mode 100644 index 00000000000..ba00ad7df4e --- /dev/null +++ b/vendor/k8s.io/gengo/examples/set-gen/sets/string.go @@ -0,0 +1,203 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +package sets + +import ( + "reflect" + "sort" +) + +// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. +type String map[string]Empty + +// NewString creates a String from a list of values. +func NewString(items ...string) String { + ss := String{} + ss.Insert(items...) + return ss +} + +// StringKeySet creates a String from a keys of a map[string](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func StringKeySet(theMap interface{}) String { + v := reflect.ValueOf(theMap) + ret := String{} + + for _, keyValue := range v.MapKeys() { + ret.Insert(keyValue.Interface().(string)) + } + return ret +} + +// Insert adds items to the set. +func (s String) Insert(items ...string) { + for _, item := range items { + s[item] = Empty{} + } +} + +// Delete removes all items from the set. +func (s String) Delete(items ...string) { + for _, item := range items { + delete(s, item) + } +} + +// Has returns true if and only if item is contained in the set. +func (s String) Has(item string) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s String) HasAll(items ...string) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s String) HasAny(items ...string) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s String) Difference(s2 String) String { + result := NewString() + for key := range s { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 String) Union(s2 String) String { + result := NewString() + for key := range s1 { + result.Insert(key) + } + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 String) Intersection(s2 String) String { + var walk, other String + result := NewString() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 String) IsSuperset(s2 String) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 String) Equal(s2 String) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +type sortableSliceOfString []string + +func (s sortableSliceOfString) Len() int { return len(s) } +func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) } +func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// List returns the contents as a sorted string slice. +func (s String) List() []string { + res := make(sortableSliceOfString, 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return []string(res) +} + +// UnsortedList returns the slice with contents in random order. +func (s String) UnsortedList() []string { + res := make([]string, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// Returns a single element from the set. +func (s String) PopAny() (string, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue string + return zeroValue, false +} + +// Len returns the size of the set. +func (s String) Len() int { + return len(s) +} + +func lessString(lhs, rhs string) bool { + return lhs < rhs +} diff --git a/vendor/k8s.io/kube-openapi/cmd/openapi-gen/args/args.go b/vendor/k8s.io/kube-openapi/cmd/openapi-gen/args/args.go new file mode 100644 index 00000000000..f555bddaf36 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/cmd/openapi-gen/args/args.go @@ -0,0 +1,73 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package args + +import ( + "fmt" + + "github.com/spf13/pflag" + "k8s.io/gengo/args" +) + +// CustomArgs is used by the gengo framework to pass args specific to this generator. +type CustomArgs struct { + // ReportFilename is added to CustomArgs for specifying name of report file used + // by API linter. If specified, API rule violations will be printed to report file. + // Otherwise default value "-" will be used which indicates stdout. + ReportFilename string +} + +// NewDefaults returns default arguments for the generator. Returning the arguments instead +// of using default flag parsing allows registering custom arguments afterwards +func NewDefaults() (*args.GeneratorArgs, *CustomArgs) { + // Default() sets a couple of flag default values for example the boilerplate. + // WithoutDefaultFlagParsing() disables implicit addition of command line flags and parsing, + // which allows registering custom arguments afterwards + genericArgs := args.Default().WithoutDefaultFlagParsing() + customArgs := &CustomArgs{} + genericArgs.CustomArgs = customArgs + + // Default value for report filename is "-", which stands for stdout + customArgs.ReportFilename = "-" + // Default value for output file base name + genericArgs.OutputFileBaseName = "openapi_generated" + + return genericArgs, customArgs +} + +// AddFlags add the generator flags to the flag set. +func (c *CustomArgs) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&c.ReportFilename, "report-filename", "r", c.ReportFilename, "Name of report file used by API linter to print API violations. Default \"-\" stands for standard output. NOTE that if valid filename other than \"-\" is specified, API linter won't return error on detected API violations. This allows further check of existing API violations without stopping the OpenAPI generation toolchain.") +} + +// Validate checks the given arguments. +func Validate(genericArgs *args.GeneratorArgs) error { + c, ok := genericArgs.CustomArgs.(*CustomArgs) + if !ok { + return fmt.Errorf("input arguments don't contain valid custom arguments") + } + if len(c.ReportFilename) == 0 { + return fmt.Errorf("report filename cannot be empty. specify a valid filename or use \"-\" for stdout") + } + if len(genericArgs.OutputFileBaseName) == 0 { + return fmt.Errorf("output file base name cannot be empty") + } + if len(genericArgs.OutputPackagePath) == 0 { + return fmt.Errorf("output package cannot be empty") + } + return nil +} diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/api_linter.go b/vendor/k8s.io/kube-openapi/pkg/generators/api_linter.go new file mode 100644 index 00000000000..9270d26320b --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/generators/api_linter.go @@ -0,0 +1,100 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generators + +import ( + "fmt" + "io" + + "k8s.io/kube-openapi/pkg/generators/rules" + + "github.com/golang/glog" + "k8s.io/gengo/types" +) + +// apiLinter is the framework hosting mutliple API rules and recording API rule +// violations +type apiLinter struct { + // API rules that implement APIRule interface and output API rule violations + rules []APIRule + violations []apiViolation +} + +// newAPILinter creates an apiLinter object with API rules in package rules. Please +// add APIRule here when new API rule is implemented. +func newAPILinter() *apiLinter { + return &apiLinter{ + rules: []APIRule{ + &rules.NamesMatch{}, + }, + } +} + +// apiViolation uniquely identifies single API rule violation +type apiViolation struct { + // Name of rule from APIRule.Name() + rule string + + packageName string + typeName string + + // Optional: name of field that violates API rule. Empty fieldName implies that + // the entire type violates the rule. + field string +} + +// APIRule is the interface for validating API rule on Go types +type APIRule interface { + // Validate evaluates API rule on type t and returns a list of field names in + // the type that violate the rule. Empty field name [""] implies the entire + // type violates the rule. + Validate(t *types.Type) ([]string, error) + + // Name returns the name of APIRule + Name() string +} + +// validate runs all API rules on type t and records any API rule violation +func (l *apiLinter) validate(t *types.Type) error { + for _, r := range l.rules { + glog.V(5).Infof("validating API rule %v for type %v", r.Name(), t) + fields, err := r.Validate(t) + if err != nil { + return err + } + for _, field := range fields { + l.violations = append(l.violations, apiViolation{ + rule: r.Name(), + packageName: t.Name.Package, + typeName: t.Name.Name, + field: field, + }) + } + } + return nil +} + +// report prints any API rule violation to writer w and returns error if violation exists +func (l *apiLinter) report(w io.Writer) error { + for _, v := range l.violations { + fmt.Fprintf(w, "API rule violation: %s,%s,%s,%s\n", v.rule, v.packageName, v.typeName, v.field) + } + if len(l.violations) > 0 { + return fmt.Errorf("API rule violations exist") + } + return nil +} diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/extension.go b/vendor/k8s.io/kube-openapi/pkg/generators/extension.go new file mode 100644 index 00000000000..befe38db248 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/generators/extension.go @@ -0,0 +1,182 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generators + +import ( + "fmt" + "sort" + "strings" + + "k8s.io/gengo/examples/set-gen/sets" + "k8s.io/gengo/types" +) + +const extensionPrefix = "x-kubernetes-" + +// extensionAttributes encapsulates common traits for particular extensions. +type extensionAttributes struct { + xName string + kind types.Kind + allowedValues sets.String +} + +// Extension tag to openapi extension attributes +var tagToExtension = map[string]extensionAttributes{ + "patchMergeKey": extensionAttributes{ + xName: "x-kubernetes-patch-merge-key", + kind: types.Slice, + }, + "patchStrategy": extensionAttributes{ + xName: "x-kubernetes-patch-strategy", + kind: types.Slice, + allowedValues: sets.NewString("merge", "retainKeys"), + }, + "listMapKey": extensionAttributes{ + xName: "x-kubernetes-list-map-keys", + kind: types.Slice, + }, + "listType": extensionAttributes{ + xName: "x-kubernetes-list-type", + kind: types.Slice, + allowedValues: sets.NewString("atomic", "set", "map"), + }, +} + +// Extension encapsulates information necessary to generate an OpenAPI extension. +type extension struct { + idlTag string // Example: listType + xName string // Example: x-kubernetes-list-type + values []string // Example: [atomic] +} + +func (e extension) hasAllowedValues() bool { + return tagToExtension[e.idlTag].allowedValues.Len() > 0 +} + +func (e extension) allowedValues() sets.String { + return tagToExtension[e.idlTag].allowedValues +} + +func (e extension) hasKind() bool { + return len(tagToExtension[e.idlTag].kind) > 0 +} + +func (e extension) kind() types.Kind { + return tagToExtension[e.idlTag].kind +} + +func (e extension) validateAllowedValues() error { + // allowedValues not set means no restrictions on values. + if !e.hasAllowedValues() { + return nil + } + // Check for missing value. + if len(e.values) == 0 { + return fmt.Errorf("%s needs a value, none given.", e.idlTag) + } + // For each extension value, validate that it is allowed. + allowedValues := e.allowedValues() + if !allowedValues.HasAll(e.values...) { + return fmt.Errorf("%v not allowed for %s. Allowed values: %v", + e.values, e.idlTag, allowedValues.List()) + } + return nil +} + +func (e extension) validateType(kind types.Kind) error { + // If this extension class has no kind, then don't validate the type. + if !e.hasKind() { + return nil + } + if kind != e.kind() { + return fmt.Errorf("tag %s on type %v; only allowed on type %v", + e.idlTag, kind, e.kind()) + } + return nil +} + +func (e extension) hasMultipleValues() bool { + return len(e.values) > 1 +} + +// Returns sorted list of map keys. Needed for deterministic testing. +func sortedMapKeys(m map[string][]string) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + return keys +} + +// Parses comments to return openapi extensions. Returns a list of +// extensions which parsed correctly, as well as a list of the +// parse errors. Validating extensions is performed separately. +// NOTE: Non-empty errors does not mean extensions is empty. +func parseExtensions(comments []string) ([]extension, []error) { + extensions := []extension{} + errors := []error{} + // First, generate extensions from "+k8s:openapi-gen=x-kubernetes-*" annotations. + values := getOpenAPITagValue(comments) + for _, val := range values { + // Example: x-kubernetes-member-tag:member_test + if strings.HasPrefix(val, extensionPrefix) { + parts := strings.SplitN(val, ":", 2) + if len(parts) != 2 { + errors = append(errors, fmt.Errorf("invalid extension value: %v", val)) + continue + } + e := extension{ + idlTag: tagName, // Example: k8s:openapi-gen + xName: parts[0], // Example: x-kubernetes-member-tag + values: []string{parts[1]}, // Example: member_test + } + extensions = append(extensions, e) + } + } + // Next, generate extensions from "idlTags" (e.g. +listType) + tagValues := types.ExtractCommentTags("+", comments) + for _, idlTag := range sortedMapKeys(tagValues) { + xAttrs, exists := tagToExtension[idlTag] + if !exists { + continue + } + values := tagValues[idlTag] + e := extension{ + idlTag: idlTag, // listType + xName: xAttrs.xName, // x-kubernetes-list-type + values: values, // [atomic] + } + extensions = append(extensions, e) + } + return extensions, errors +} + +func validateMemberExtensions(extensions []extension, m *types.Member) []error { + errors := []error{} + for _, e := range extensions { + if err := e.validateAllowedValues(); err != nil { + errors = append(errors, err) + } + if err := e.validateType(m.Type.Kind); err != nil { + errors = append(errors, err) + } + } + return errors +} diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go b/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go new file mode 100644 index 00000000000..d6c6275a78f --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/generators/openapi.go @@ -0,0 +1,704 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package generators + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + + "k8s.io/gengo/args" + "k8s.io/gengo/generator" + "k8s.io/gengo/namer" + "k8s.io/gengo/types" + generatorargs "k8s.io/kube-openapi/cmd/openapi-gen/args" + openapi "k8s.io/kube-openapi/pkg/common" + + "github.com/golang/glog" +) + +// This is the comment tag that carries parameters for open API generation. +const tagName = "k8s:openapi-gen" +const tagOptional = "optional" + +// Known values for the tag. +const ( + tagValueTrue = "true" + tagValueFalse = "false" +) + +// Used for temporary validation of patch struct tags. +// TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server. +var tempPatchTags = [...]string{ + "patchMergeKey", + "patchStrategy", +} + +func getOpenAPITagValue(comments []string) []string { + return types.ExtractCommentTags("+", comments)[tagName] +} + +func getSingleTagsValue(comments []string, tag string) (string, error) { + tags, ok := types.ExtractCommentTags("+", comments)[tag] + if !ok || len(tags) == 0 { + return "", nil + } + if len(tags) > 1 { + return "", fmt.Errorf("multiple values are not allowed for tag %s", tag) + } + return tags[0], nil +} + +func hasOpenAPITagValue(comments []string, value string) bool { + tagValues := getOpenAPITagValue(comments) + for _, val := range tagValues { + if val == value { + return true + } + } + return false +} + +// hasOptionalTag returns true if the member has +optional in its comments or +// omitempty in its json tags. +func hasOptionalTag(m *types.Member) bool { + hasOptionalCommentTag := types.ExtractCommentTags( + "+", m.CommentLines)[tagOptional] != nil + hasOptionalJsonTag := strings.Contains( + reflect.StructTag(m.Tags).Get("json"), "omitempty") + return hasOptionalCommentTag || hasOptionalJsonTag +} + +type identityNamer struct{} + +func (_ identityNamer) Name(t *types.Type) string { + return t.Name.String() +} + +var _ namer.Namer = identityNamer{} + +// NameSystems returns the name system used by the generators in this package. +func NameSystems() namer.NameSystems { + return namer.NameSystems{ + "raw": namer.NewRawNamer("", nil), + "sorting_namer": identityNamer{}, + } +} + +// DefaultNameSystem returns the default name system for ordering the types to be +// processed by the generators in this package. +func DefaultNameSystem() string { + return "sorting_namer" +} + +func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { + boilerplate, err := arguments.LoadGoBoilerplate() + if err != nil { + glog.Fatalf("Failed loading boilerplate: %v", err) + } + header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...) + header = append(header, []byte( + ` +// This file was autogenerated by openapi-gen. Do not edit it manually! + +`)...) + + reportFilename := "-" + if customArgs, ok := arguments.CustomArgs.(*generatorargs.CustomArgs); ok { + reportFilename = customArgs.ReportFilename + } + + return generator.Packages{ + &generator.DefaultPackage{ + PackageName: filepath.Base(arguments.OutputPackagePath), + PackagePath: arguments.OutputPackagePath, + HeaderText: header, + GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { + return []generator.Generator{NewOpenAPIGen(arguments.OutputFileBaseName, arguments.OutputPackagePath, context, newAPILinter(), reportFilename)} + }, + FilterFunc: func(c *generator.Context, t *types.Type) bool { + // There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen + if strings.HasPrefix(t.Name.Name, "codecSelfer") { + return false + } + pkg := context.Universe.Package(t.Name.Package) + if hasOpenAPITagValue(pkg.Comments, tagValueTrue) { + return !hasOpenAPITagValue(t.CommentLines, tagValueFalse) + } + if hasOpenAPITagValue(t.CommentLines, tagValueTrue) { + return true + } + return false + }, + }, + } +} + +const ( + specPackagePath = "github.com/go-openapi/spec" + openAPICommonPackagePath = "k8s.io/kube-openapi/pkg/common" +) + +// openApiGen produces a file with auto-generated OpenAPI functions. +type openAPIGen struct { + generator.DefaultGen + // TargetPackage is the package that will get GetOpenAPIDefinitions function returns all open API definitions. + targetPackage string + imports namer.ImportTracker + types []*types.Type + context *generator.Context + linter *apiLinter + reportFilename string +} + +func NewOpenAPIGen(sanitizedName string, targetPackage string, context *generator.Context, linter *apiLinter, reportFilename string) generator.Generator { + return &openAPIGen{ + DefaultGen: generator.DefaultGen{ + OptionalName: sanitizedName, + }, + imports: generator.NewImportTracker(), + targetPackage: targetPackage, + context: context, + linter: linter, + reportFilename: reportFilename, + } +} + +const nameTmpl = "schema_$.type|private$" + +func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems { + // Have the raw namer for this file track what it imports. + return namer.NameSystems{ + "raw": namer.NewRawNamer(g.targetPackage, g.imports), + "private": &namer.NameStrategy{ + Join: func(pre string, in []string, post string) string { + return strings.Join(in, "_") + }, + PrependPackageNames: 4, // enough to fully qualify from k8s.io/api/... + }, + } +} + +func (g *openAPIGen) Filter(c *generator.Context, t *types.Type) bool { + // There is a conflict between this codegen and codecgen, we should avoid types generated for codecgen + if strings.HasPrefix(t.Name.Name, "codecSelfer") { + return false + } + g.types = append(g.types, t) + return true +} + +func (g *openAPIGen) isOtherPackage(pkg string) bool { + if pkg == g.targetPackage { + return false + } + if strings.HasSuffix(pkg, "\""+g.targetPackage+"\"") { + return false + } + return true +} + +func (g *openAPIGen) Imports(c *generator.Context) []string { + importLines := []string{} + for _, singleImport := range g.imports.ImportLines() { + importLines = append(importLines, singleImport) + } + return importLines +} + +func argsFromType(t *types.Type) generator.Args { + return generator.Args{ + "type": t, + "ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"), + "OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"), + "SpecSchemaType": types.Ref(specPackagePath, "Schema"), + } +} + +func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error { + sw := generator.NewSnippetWriter(w, c, "$", "$") + sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil)) + sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil)) + + for _, t := range g.types { + err := newOpenAPITypeWriter(sw).generateCall(t) + if err != nil { + return err + } + } + + sw.Do("}\n", nil) + sw.Do("}\n\n", nil) + + return sw.Error() +} + +func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error { + glog.V(5).Infof("validating API rules for type %v", t) + if err := g.linter.validate(t); err != nil { + return err + } + glog.V(5).Infof("generating for type %v", t) + sw := generator.NewSnippetWriter(w, c, "$", "$") + err := newOpenAPITypeWriter(sw).generate(t) + if err != nil { + return err + } + return sw.Error() +} + +func getJsonTags(m *types.Member) []string { + jsonTag := reflect.StructTag(m.Tags).Get("json") + if jsonTag == "" { + return []string{} + } + return strings.Split(jsonTag, ",") +} + +func getReferableName(m *types.Member) string { + jsonTags := getJsonTags(m) + if len(jsonTags) > 0 { + if jsonTags[0] == "-" { + return "" + } else { + return jsonTags[0] + } + } else { + return m.Name + } +} + +func shouldInlineMembers(m *types.Member) bool { + jsonTags := getJsonTags(m) + return len(jsonTags) > 1 && jsonTags[1] == "inline" +} + +type openAPITypeWriter struct { + *generator.SnippetWriter + refTypes map[string]*types.Type + GetDefinitionInterface *types.Type +} + +func newOpenAPITypeWriter(sw *generator.SnippetWriter) openAPITypeWriter { + return openAPITypeWriter{ + SnippetWriter: sw, + refTypes: map[string]*types.Type{}, + } +} + +func methodReturnsValue(mt *types.Type, pkg, name string) bool { + if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 { + return false + } + r := mt.Signature.Results[0] + return r.Name.Name == name && r.Name.Package == pkg +} + +func hasOpenAPIDefinitionMethod(t *types.Type) bool { + for mn, mt := range t.Methods { + if mn != "OpenAPIDefinition" { + continue + } + return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition") + } + return false +} + +func hasOpenAPIDefinitionMethods(t *types.Type) bool { + var hasSchemaTypeMethod, hasOpenAPISchemaFormat bool + for mn, mt := range t.Methods { + switch mn { + case "OpenAPISchemaType": + hasSchemaTypeMethod = methodReturnsValue(mt, "", "[]string") + case "OpenAPISchemaFormat": + hasOpenAPISchemaFormat = methodReturnsValue(mt, "", "string") + } + } + return hasSchemaTypeMethod && hasOpenAPISchemaFormat +} + +// typeShortName returns short package name (e.g. the name x appears in package x definition) dot type name. +func typeShortName(t *types.Type) string { + return filepath.Base(t.Name.Package) + "." + t.Name.Name +} + +func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) { + var err error + for _, m := range t.Members { + if hasOpenAPITagValue(m.CommentLines, tagValueFalse) { + continue + } + if shouldInlineMembers(&m) { + required, err = g.generateMembers(m.Type, required) + if err != nil { + return required, err + } + continue + } + name := getReferableName(&m) + if name == "" { + continue + } + if !hasOptionalTag(&m) { + required = append(required, name) + } + if err = g.generateProperty(&m, t); err != nil { + glog.Errorf("Error when generating: %v, %v\n", name, m) + return required, err + } + } + return required, nil +} + +func (g openAPITypeWriter) generateCall(t *types.Type) error { + // Only generate for struct type and ignore the rest + switch t.Kind { + case types.Struct: + args := argsFromType(t) + g.Do("\"$.$\": ", t.Name) + if hasOpenAPIDefinitionMethod(t) { + g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args) + } else { + g.Do(nameTmpl+"(ref),\n", args) + } + } + return g.Error() +} + +func (g openAPITypeWriter) generate(t *types.Type) error { + // Only generate for struct type and ignore the rest + switch t.Kind { + case types.Struct: + if hasOpenAPIDefinitionMethod(t) { + // already invoked directly + return nil + } + + args := argsFromType(t) + g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args) + if hasOpenAPIDefinitionMethods(t) { + g.Do("return $.OpenAPIDefinition|raw${\n"+ + "Schema: spec.Schema{\n"+ + "SchemaProps: spec.SchemaProps{\n", args) + g.generateDescription(t.CommentLines) + g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+ + "Format:$.type|raw${}.OpenAPISchemaFormat(),\n"+ + "},\n"+ + "},\n"+ + "}\n}\n\n", args) + return nil + } + g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args) + g.generateDescription(t.CommentLines) + g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args) + required, err := g.generateMembers(t, []string{}) + if err != nil { + return err + } + g.Do("},\n", nil) + if len(required) > 0 { + g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\"")) + } + g.Do("},\n", nil) + if err := g.generateStructExtensions(t); err != nil { + return err + } + g.Do("},\n", nil) + g.Do("Dependencies: []string{\n", args) + // Map order is undefined, sort them or we may get a different file generated each time. + keys := []string{} + for k := range g.refTypes { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := g.refTypes[k] + if t, _ := openapi.GetOpenAPITypeFormat(v.String()); t != "" { + // This is a known type, we do not need a reference to it + // Will eliminate special case of time.Time + continue + } + g.Do("\"$.$\",", k) + } + g.Do("},\n}\n}\n\n", nil) + } + return nil +} + +func (g openAPITypeWriter) generateStructExtensions(t *types.Type) error { + extensions, errors := parseExtensions(t.CommentLines) + // Initially, we will only log struct extension errors. + if len(errors) > 0 { + for _, e := range errors { + glog.V(2).Infof("[%s]: %s\n", t.String(), e) + } + } + // TODO(seans3): Validate struct extensions here. + g.emitExtensions(extensions) + return nil +} + +func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type) error { + extensions, parseErrors := parseExtensions(m.CommentLines) + validationErrors := validateMemberExtensions(extensions, m) + errors := append(parseErrors, validationErrors...) + // Initially, we will only log member extension errors. + if len(errors) > 0 { + errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String()) + for _, e := range errors { + glog.V(2).Infof("%s %s\n", errorPrefix, e) + } + } + g.emitExtensions(extensions) + return nil +} + +func (g openAPITypeWriter) emitExtensions(extensions []extension) { + // If any extensions exist, then emit code to create them. + if len(extensions) == 0 { + return + } + g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil) + for _, extension := range extensions { + g.Do("\"$.$\": ", extension.xName) + if extension.hasMultipleValues() { + g.Do("[]string{\n", nil) + } + for _, value := range extension.values { + g.Do("\"$.$\",\n", value) + } + if extension.hasMultipleValues() { + g.Do("},\n", nil) + } + } + g.Do("},\n},\n", nil) +} + +// TODO(#44005): Move this validation outside of this generator (probably to policy verifier) +func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error { + // TODO: Remove patch struct tag validation because they we are now consuming OpenAPI on server. + for _, tagKey := range tempPatchTags { + structTagValue := reflect.StructTag(m.Tags).Get(tagKey) + commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey) + if err != nil { + return err + } + if structTagValue != commentTagValue { + return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)", + m.Name, parent.Name.String()) + } + } + return nil +} + +func (g openAPITypeWriter) generateDescription(CommentLines []string) { + var buffer bytes.Buffer + delPrevChar := func() { + if buffer.Len() > 0 { + buffer.Truncate(buffer.Len() - 1) // Delete the last " " or "\n" + } + } + + for _, line := range CommentLines { + // Ignore all lines after --- + if line == "---" { + break + } + line = strings.TrimRight(line, " ") + leading := strings.TrimLeft(line, " ") + switch { + case len(line) == 0: // Keep paragraphs + delPrevChar() + buffer.WriteString("\n\n") + case strings.HasPrefix(leading, "TODO"): // Ignore one line TODOs + case strings.HasPrefix(leading, "+"): // Ignore instructions to go2idl + default: + if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { + delPrevChar() + line = "\n" + line + "\n" // Replace it with newline. This is useful when we have a line with: "Example:\n\tJSON-someting..." + } else { + line += " " + } + buffer.WriteString(line) + } + } + + postDoc := strings.TrimRight(buffer.String(), "\n") + postDoc = strings.Replace(postDoc, "\\\"", "\"", -1) // replace user's \" to " + postDoc = strings.Replace(postDoc, "\"", "\\\"", -1) // Escape " + postDoc = strings.Replace(postDoc, "\n", "\\n", -1) + postDoc = strings.Replace(postDoc, "\t", "\\t", -1) + postDoc = strings.Trim(postDoc, " ") + if postDoc != "" { + g.Do("Description: \"$.$\",\n", postDoc) + } +} + +func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error { + name := getReferableName(m) + if name == "" { + return nil + } + if err := g.validatePatchTags(m, parent); err != nil { + return err + } + g.Do("\"$.$\": {\n", name) + if err := g.generateMemberExtensions(m, parent); err != nil { + return err + } + g.Do("SchemaProps: spec.SchemaProps{\n", nil) + g.generateDescription(m.CommentLines) + jsonTags := getJsonTags(m) + if len(jsonTags) > 1 && jsonTags[1] == "string" { + g.generateSimpleProperty("string", "") + g.Do("},\n},\n", nil) + return nil + } + t := resolveAliasAndPtrType(m.Type) + // If we can get a openAPI type and format for this type, we consider it to be simple property + typeString, format := openapi.GetOpenAPITypeFormat(t.String()) + if typeString != "" { + g.generateSimpleProperty(typeString, format) + g.Do("},\n},\n", nil) + return nil + } + switch t.Kind { + case types.Builtin: + return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t) + case types.Map: + if err := g.generateMapProperty(t); err != nil { + return err + } + case types.Slice, types.Array: + if err := g.generateSliceProperty(t); err != nil { + return err + } + case types.Struct, types.Interface: + g.generateReferenceProperty(t) + default: + return fmt.Errorf("cannot generate spec for type %v", t) + } + g.Do("},\n},\n", nil) + return g.Error() +} + +func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) { + g.Do("Type: []string{\"$.$\"},\n", typeString) + g.Do("Format: \"$.$\",\n", format) +} + +func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) { + g.refTypes[t.Name.String()] = t + g.Do("Ref: ref(\"$.$\"),\n", t.Name.String()) +} + +func resolveAliasAndPtrType(t *types.Type) *types.Type { + var prev *types.Type + for prev != t { + prev = t + if t.Kind == types.Alias { + t = t.Underlying + } + if t.Kind == types.Pointer { + t = t.Elem + } + } + return t +} + +func (g openAPITypeWriter) generateMapProperty(t *types.Type) error { + keyType := resolveAliasAndPtrType(t.Key) + elemType := resolveAliasAndPtrType(t.Elem) + + // According to OpenAPI examples, only map from string is supported + if keyType.Name.Name != "string" { + return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t) + } + g.Do("Type: []string{\"object\"},\n", nil) + g.Do("AdditionalProperties: &spec.SchemaOrBool{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil) + typeString, format := openapi.GetOpenAPITypeFormat(elemType.String()) + if typeString != "" { + g.generateSimpleProperty(typeString, format) + g.Do("},\n},\n},\n", nil) + return nil + } + switch elemType.Kind { + case types.Builtin: + return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType) + case types.Struct: + g.generateReferenceProperty(elemType) + case types.Slice, types.Array: + g.generateSliceProperty(elemType) + default: + return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name) + } + g.Do("},\n},\n},\n", nil) + return nil +} + +func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error { + elemType := resolveAliasAndPtrType(t.Elem) + g.Do("Type: []string{\"array\"},\n", nil) + g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil) + typeString, format := openapi.GetOpenAPITypeFormat(elemType.String()) + if typeString != "" { + g.generateSimpleProperty(typeString, format) + g.Do("},\n},\n},\n", nil) + return nil + } + switch elemType.Kind { + case types.Builtin: + return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType) + case types.Struct: + g.generateReferenceProperty(elemType) + case types.Slice, types.Array: + g.generateSliceProperty(elemType) + default: + return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t) + } + g.Do("},\n},\n},\n", nil) + return nil +} + +// Finalize prints the API rule violations to report file (if specified from arguments) or stdout (default) +func (g *openAPIGen) Finalize(c *generator.Context, w io.Writer) error { + // If report file isn't specified, return error to force user to choose either stdout ("-") or a file name + if len(g.reportFilename) == 0 { + return fmt.Errorf("empty report file name: please provide a valid file name or use the default \"-\" (stdout)") + } + // If stdout is specified, print violations and return error + if g.reportFilename == "-" { + return g.linter.report(os.Stdout) + } + // Otherwise, print violations to report file and return nil + f, err := os.Create(g.reportFilename) + if err != nil { + return err + } + defer f.Close() + g.linter.report(f) + // NOTE: we don't return error here because we assume that the report file will + // get evaluated afterwards to determine if error should be raised. For example, + // you can have make rules that compare the report file with existing known + // violations (whitelist) and determine no error if no change is detected. + return nil +} diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go b/vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go new file mode 100644 index 00000000000..384a44dca07 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/generators/rules/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package rules contains API rules that are enforced in OpenAPI spec generation +// as part of the machinery. Files under this package implement APIRule interface +// which evaluates Go type and produces list of API rule violations. +// +// Implementations of APIRule should be added to API linter under openAPIGen code- +// generator to get integrated in the generation process. +package rules diff --git a/vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match.go b/vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match.go new file mode 100644 index 00000000000..3a71ff178be --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/generators/rules/names_match.go @@ -0,0 +1,172 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rules + +import ( + "reflect" + "strings" + + "k8s.io/kube-openapi/pkg/util/sets" + + "k8s.io/gengo/types" +) + +var ( + // Blacklist of JSON tags that should skip match evaluation + jsonTagBlacklist = sets.NewString( + // Omitted field is ignored by the package + "-", + ) + + // Blacklist of JSON names that should skip match evaluation + jsonNameBlacklist = sets.NewString( + // Empty name is used for inline struct field (e.g. metav1.TypeMeta) + "", + // Special case for object and list meta + "metadata", + ) + + // List of substrings that aren't allowed in Go name and JSON name + disallowedNameSubstrings = sets.NewString( + // Underscore is not allowed in either name + "_", + // Dash is not allowed in either name. Note that since dash is a valid JSON tag, this should be checked + // after JSON tag blacklist check. + "-", + ) +) + +/* +NamesMatch implements APIRule interface. +Go field names must be CamelCase. JSON field names must be camelCase. Other than capitalization of the +initial letter, the two should almost always match. No underscores nor dashes in either. +This rule verifies the convention "Other than capitalization of the initial letter, the two should almost always match." +Examples (also in unit test): + Go name | JSON name | match + podSpec false + PodSpec podSpec true + PodSpec PodSpec false + podSpec podSpec false + PodSpec spec false + Spec podSpec false + JSONSpec jsonSpec true + JSONSpec jsonspec false + HTTPJSONSpec httpJSONSpec true +NOTE: this validator cannot tell two sequential all-capital words from one word, therefore the case below +is also considered matched. + HTTPJSONSpec httpjsonSpec true +NOTE: JSON names in jsonNameBlacklist should skip evaluation + true + podSpec true + podSpec - true + podSpec metadata true +*/ +type NamesMatch struct{} + +// Name returns the name of APIRule +func (n *NamesMatch) Name() string { + return "names_match" +} + +// Validate evaluates API rule on type t and returns a list of field names in +// the type that violate the rule. Empty field name [""] implies the entire +// type violates the rule. +func (n *NamesMatch) Validate(t *types.Type) ([]string, error) { + fields := make([]string, 0) + + // Only validate struct type and ignore the rest + switch t.Kind { + case types.Struct: + for _, m := range t.Members { + goName := m.Name + jsonTag, ok := reflect.StructTag(m.Tags).Lookup("json") + // Distinguish empty JSON tag and missing JSON tag. Empty JSON tag / name is + // allowed (in JSON name blacklist) but missing JSON tag is invalid. + if !ok { + fields = append(fields, goName) + continue + } + if jsonTagBlacklist.Has(jsonTag) { + continue + } + jsonName := strings.Split(jsonTag, ",")[0] + if !namesMatch(goName, jsonName) { + fields = append(fields, goName) + } + } + } + return fields, nil +} + +// namesMatch evaluates if goName and jsonName match the API rule +// TODO: Use an off-the-shelf CamelCase solution instead of implementing this logic. The following existing +// packages have been tried out: +// github.com/markbates/inflect +// github.com/segmentio/go-camelcase +// github.com/iancoleman/strcase +// github.com/fatih/camelcase +// Please see https://github.com/kubernetes/kube-openapi/pull/83#issuecomment-400842314 for more details +// about why they don't satisfy our need. What we need can be a function that detects an acronym at the +// beginning of a string. +func namesMatch(goName, jsonName string) bool { + if jsonNameBlacklist.Has(jsonName) { + return true + } + if !isAllowedName(goName) || !isAllowedName(jsonName) { + return false + } + if strings.ToLower(goName) != strings.ToLower(jsonName) { + return false + } + // Go field names must be CamelCase. JSON field names must be camelCase. + if !isCapital(goName[0]) || isCapital(jsonName[0]) { + return false + } + for i := 0; i < len(goName); i++ { + if goName[i] == jsonName[i] { + // goName[0:i-1] is uppercase and jsonName[0:i-1] is lowercase, goName[i:] + // and jsonName[i:] should match; + // goName[i] should be lowercase if i is equal to 1, e.g.: + // goName | jsonName + // PodSpec podSpec + // or uppercase if i is greater than 1, e.g.: + // goname | jsonName + // JSONSpec jsonSpec + // This is to rule out cases like: + // goname | jsonName + // JSONSpec jsonspec + return goName[i:] == jsonName[i:] && (i == 1 || isCapital(goName[i])) + } + } + return true +} + +// isCaptical returns true if one character is capital +func isCapital(b byte) bool { + return b >= 'A' && b <= 'Z' +} + +// isAllowedName checks the list of disallowedNameSubstrings and returns true if name doesn't contain +// any disallowed substring. +func isAllowedName(name string) bool { + for _, substr := range disallowedNameSubstrings.UnsortedList() { + if strings.Contains(name, substr) { + return false + } + } + return true +} diff --git a/vendor/k8s.io/kube-openapi/pkg/util/sets/empty.go b/vendor/k8s.io/kube-openapi/pkg/util/sets/empty.go new file mode 100644 index 00000000000..13303ea8900 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/util/sets/empty.go @@ -0,0 +1,27 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +// NOTE: This file is copied from k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/sets/empty.go +// because in Kubernetes we don't allowed vendor code to import staging code. See +// https://github.com/kubernetes/kube-openapi/pull/90 for more details. + +package sets + +// Empty is public since it is used by some internal API objects for conversions between external +// string arrays and internal sets, and conversion logic requires public types today. +type Empty struct{} diff --git a/vendor/k8s.io/kube-openapi/pkg/util/sets/string.go b/vendor/k8s.io/kube-openapi/pkg/util/sets/string.go new file mode 100644 index 00000000000..53f2bc12aae --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/util/sets/string.go @@ -0,0 +1,207 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by set-gen. DO NOT EDIT. + +// NOTE: This file is copied from k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/util/sets/string.go +// because in Kubernetes we don't allowed vendor code to import staging code. See +// https://github.com/kubernetes/kube-openapi/pull/90 for more details. + +package sets + +import ( + "reflect" + "sort" +) + +// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. +type String map[string]Empty + +// NewString creates a String from a list of values. +func NewString(items ...string) String { + ss := String{} + ss.Insert(items...) + return ss +} + +// StringKeySet creates a String from a keys of a map[string](? extends interface{}). +// If the value passed in is not actually a map, this will panic. +func StringKeySet(theMap interface{}) String { + v := reflect.ValueOf(theMap) + ret := String{} + + for _, keyValue := range v.MapKeys() { + ret.Insert(keyValue.Interface().(string)) + } + return ret +} + +// Insert adds items to the set. +func (s String) Insert(items ...string) { + for _, item := range items { + s[item] = Empty{} + } +} + +// Delete removes all items from the set. +func (s String) Delete(items ...string) { + for _, item := range items { + delete(s, item) + } +} + +// Has returns true if and only if item is contained in the set. +func (s String) Has(item string) bool { + _, contained := s[item] + return contained +} + +// HasAll returns true if and only if all items are contained in the set. +func (s String) HasAll(items ...string) bool { + for _, item := range items { + if !s.Has(item) { + return false + } + } + return true +} + +// HasAny returns true if any items are contained in the set. +func (s String) HasAny(items ...string) bool { + for _, item := range items { + if s.Has(item) { + return true + } + } + return false +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s String) Difference(s2 String) String { + result := NewString() + for key := range s { + if !s2.Has(key) { + result.Insert(key) + } + } + return result +} + +// Union returns a new set which includes items in either s1 or s2. +// For example: +// s1 = {a1, a2} +// s2 = {a3, a4} +// s1.Union(s2) = {a1, a2, a3, a4} +// s2.Union(s1) = {a1, a2, a3, a4} +func (s1 String) Union(s2 String) String { + result := NewString() + for key := range s1 { + result.Insert(key) + } + for key := range s2 { + result.Insert(key) + } + return result +} + +// Intersection returns a new set which includes the item in BOTH s1 and s2 +// For example: +// s1 = {a1, a2} +// s2 = {a2, a3} +// s1.Intersection(s2) = {a2} +func (s1 String) Intersection(s2 String) String { + var walk, other String + result := NewString() + if s1.Len() < s2.Len() { + walk = s1 + other = s2 + } else { + walk = s2 + other = s1 + } + for key := range walk { + if other.Has(key) { + result.Insert(key) + } + } + return result +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 String) IsSuperset(s2 String) bool { + for item := range s2 { + if !s1.Has(item) { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 String) Equal(s2 String) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +type sortableSliceOfString []string + +func (s sortableSliceOfString) Len() int { return len(s) } +func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) } +func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// List returns the contents as a sorted string slice. +func (s String) List() []string { + res := make(sortableSliceOfString, 0, len(s)) + for key := range s { + res = append(res, key) + } + sort.Sort(res) + return []string(res) +} + +// UnsortedList returns the slice with contents in random order. +func (s String) UnsortedList() []string { + res := make([]string, 0, len(s)) + for key := range s { + res = append(res, key) + } + return res +} + +// Returns a single element from the set. +func (s String) PopAny() (string, bool) { + for key := range s { + s.Delete(key) + return key, true + } + var zeroValue string + return zeroValue, false +} + +// Len returns the size of the set. +func (s String) Len() int { + return len(s) +} + +func lessString(lhs, rhs string) bool { + return lhs < rhs +} From 01522d3c4a8dced6ce51899f0048097cea3bace6 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 10:41:36 -0700 Subject: [PATCH 04/35] remove code generators from tools.go --- internal/pkg/scaffold/tools.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/pkg/scaffold/tools.go b/internal/pkg/scaffold/tools.go index d37d4765e07..a04b4940858 100644 --- a/internal/pkg/scaffold/tools.go +++ b/internal/pkg/scaffold/tools.go @@ -37,14 +37,6 @@ const toolsTmpl = `// +build tools package tools import ( - // Code generators built at runtime. - _ "k8s.io/code-generator/cmd/client-gen" - _ "k8s.io/code-generator/cmd/conversion-gen" - _ "k8s.io/code-generator/cmd/deepcopy-gen" - _ "k8s.io/code-generator/cmd/informer-gen" - _ "k8s.io/code-generator/cmd/lister-gen" - _ "k8s.io/gengo/args" - _ "k8s.io/kube-openapi/cmd/openapi-gen" _ "sigs.k8s.io/controller-tools/pkg/crd/generator" ) ` From 2b92c73a1f1e8f9ec43801068af56aa675f7bb64 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 12:24:49 -0700 Subject: [PATCH 05/35] use relative package path instead of non default deepcopy args --- cmd/operator-sdk/internal/genutil/k8s.go | 23 +++++++++++--------- cmd/operator-sdk/internal/genutil/openapi.go | 2 -- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cmd/operator-sdk/internal/genutil/k8s.go b/cmd/operator-sdk/internal/genutil/k8s.go index 659bde5b6a1..b2ff4c45821 100644 --- a/cmd/operator-sdk/internal/genutil/k8s.go +++ b/cmd/operator-sdk/internal/genutil/k8s.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" generatorargs "k8s.io/code-generator/cmd/deepcopy-gen/args" + gengoargs "k8s.io/gengo/args" "k8s.io/gengo/examples/deepcopy-gen/generators" ) @@ -66,17 +67,19 @@ func deepcopyGen(hf string, fqApis []string) error { } flag.Set("logtostderr", "true") for _, api := range fqApis { - apisIdx := strings.Index(api, scaffold.ApisDir) - // deepcopy-gen does not write to the target directory unless defaults - // are used for some reason. - args, cargs := generatorargs.NewDefaults() - args.InputDirs = []string{api} - args.OutputFileBaseName = "zz_generated.deepcopy" - args.OutputPackagePath = filepath.Join(wd, api[apisIdx:]) - args.GoHeaderFilePath = hf - cargs.BoundingDirs = []string{api} + // Use relative API path so the generator writes to the correct path. + apiPath := "./" + api[strings.Index(api, scaffold.ApisDir):] + args := &gengoargs.GeneratorArgs{ + InputDirs: []string{apiPath}, + OutputPackagePath: filepath.Join(wd, apiPath), + OutputFileBaseName: "zz_generated.deepcopy", + GoHeaderFilePath: hf, + } + cargs := &generatorargs.CustomArgs{ + BoundingDirs: []string{apiPath}, + } + // Cast for use upstream. args.CustomArgs = (*generators.CustomArgs)(cargs) - if err := generatorargs.Validate(args); err != nil { return errors.Wrap(err, "deepcopy-gen argument validation error") } diff --git a/cmd/operator-sdk/internal/genutil/openapi.go b/cmd/operator-sdk/internal/genutil/openapi.go index fd2f1d1a026..e687d2d6180 100644 --- a/cmd/operator-sdk/internal/genutil/openapi.go +++ b/cmd/operator-sdk/internal/genutil/openapi.go @@ -110,12 +110,10 @@ func openAPIGen(hf string, fqApis []string) error { ReportFilename: "-", // stdout }, } - if err := generatorargs.Validate(args); err != nil { return errors.Wrap(err, "openapi-gen argument validation error") } - // Generates the code for the OpenAPIDefinitions. err := args.Execute( generators.NameSystems(), generators.DefaultNameSystem(), From a36456e76ac4b33ff48f5f147fd8731a8f093c09 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 13:07:56 -0700 Subject: [PATCH 06/35] check if in /home/estroz/go/src before running deepcopy generator --- cmd/operator-sdk/internal/genutil/k8s.go | 28 ++++++++++++-------- cmd/operator-sdk/internal/genutil/openapi.go | 7 ++--- internal/util/projutil/exec.go | 4 +-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/cmd/operator-sdk/internal/genutil/k8s.go b/cmd/operator-sdk/internal/genutil/k8s.go index b2ff4c45821..95ead24a877 100644 --- a/cmd/operator-sdk/internal/genutil/k8s.go +++ b/cmd/operator-sdk/internal/genutil/k8s.go @@ -27,7 +27,6 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" generatorargs "k8s.io/code-generator/cmd/deepcopy-gen/args" - gengoargs "k8s.io/gengo/args" "k8s.io/gengo/examples/deepcopy-gen/generators" ) @@ -69,22 +68,29 @@ func deepcopyGen(hf string, fqApis []string) error { for _, api := range fqApis { // Use relative API path so the generator writes to the correct path. apiPath := "./" + api[strings.Index(api, scaffold.ApisDir):] - args := &gengoargs.GeneratorArgs{ - InputDirs: []string{apiPath}, - OutputPackagePath: filepath.Join(wd, apiPath), - OutputFileBaseName: "zz_generated.deepcopy", - GoHeaderFilePath: hf, + args, cargs := generatorargs.NewDefaults() + args.InputDirs = []string{apiPath} + args.OutputPackagePath = filepath.Join(wd, apiPath) + args.OutputFileBaseName = "zz_generated.deepcopy" + args.GoHeaderFilePath = hf + cargs.BoundingDirs = []string{apiPath} + // deepcopy-gen will use the import path of an API if in $GOPATH/src, but + // if we're outside of that dir it'll use apiPath. In order to generate + // deepcopy code at the correct path in all cases, we must unset the + // base output dir, which is $GOPATH/src by default, when we're outside. + inGopathSrc, err := projutil.WdInGoPathSrc() + if err != nil { + return err } - cargs := &generatorargs.CustomArgs{ - BoundingDirs: []string{apiPath}, + if !inGopathSrc { + args.OutputBase = "" } - // Cast for use upstream. - args.CustomArgs = (*generators.CustomArgs)(cargs) + if err := generatorargs.Validate(args); err != nil { return errors.Wrap(err, "deepcopy-gen argument validation error") } - err := args.Execute( + err = args.Execute( generators.NameSystems(), generators.DefaultNameSystem(), generators.Packages, diff --git a/cmd/operator-sdk/internal/genutil/openapi.go b/cmd/operator-sdk/internal/genutil/openapi.go index e687d2d6180..985179f188e 100644 --- a/cmd/operator-sdk/internal/genutil/openapi.go +++ b/cmd/operator-sdk/internal/genutil/openapi.go @@ -100,11 +100,12 @@ func openAPIGen(hf string, fqApis []string) error { } flag.Set("logtostderr", "true") for _, api := range fqApis { - apisIdx := strings.Index(api, scaffold.ApisDir) + // Use relative API path so the generator writes to the correct path. + apiPath := "./" + api[strings.Index(api, scaffold.ApisDir):] args := &gengoargs.GeneratorArgs{ - InputDirs: []string{api}, + InputDirs: []string{apiPath}, OutputFileBaseName: "zz_generated.openapi", - OutputPackagePath: filepath.Join(wd, api[apisIdx:]), + OutputPackagePath: filepath.Join(wd, apiPath), GoHeaderFilePath: hf, CustomArgs: &generatorargs.CustomArgs{ ReportFilename: "-", // stdout diff --git a/internal/util/projutil/exec.go b/internal/util/projutil/exec.go index 1fde21f913a..b2b2e270eef 100644 --- a/internal/util/projutil/exec.go +++ b/internal/util/projutil/exec.go @@ -137,14 +137,14 @@ func GoModOn() (bool, error) { if v == "on" { return true, nil } - inSrc, err := wdInGoPathSrc() + inSrc, err := WdInGoPathSrc() if err != nil { return false, err } return !inSrc && (!ok || v == "" || v == "auto"), nil } -func wdInGoPathSrc() (bool, error) { +func WdInGoPathSrc() (bool, error) { wd, err := os.Getwd() if err != nil { return false, err From e1488640a5dc084cb584fcc1d20b7b7ab6603185 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 16:55:25 -0700 Subject: [PATCH 07/35] *.md: remove /home/estroz/go references --- README.md | 4 ++-- doc/dev/testing/running-the-tests.md | 4 ++-- doc/sdk-cli-reference.md | 4 ++-- doc/user-guide.md | 16 ++++++++-------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e044c88fa00..825670364b1 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,9 @@ Follow the steps in the [installation guide][install_guide] to learn how to inst ```sh # Create an app-operator project that defines the App CR. -$ mkdir -p $GOPATH/src/github.com/example-inc/ +$ mkdir -p $HOME/projects/example-inc/ # Create a new app-operator project -$ cd $GOPATH/src/github.com/example-inc/ +$ cd $HOME/projects/example-inc/ $ export GO111MODULE=on $ operator-sdk new app-operator $ cd app-operator diff --git a/doc/dev/testing/running-the-tests.md b/doc/dev/testing/running-the-tests.md index bfca7c6c5d0..f931d7b4a49 100644 --- a/doc/dev/testing/running-the-tests.md +++ b/doc/dev/testing/running-the-tests.md @@ -117,8 +117,8 @@ during the go tests can cause these cleanups to fail (the ansible and helm E2E t always clean up correctly). For example, if a segfault occurs or a user kills the testing process, the cleanup functions for the go tests will not run. To manually clean up a test: -1. Delete the CRD (`kubectl delete -f $GOPATH/src/github.com/example-inc/memcached-operator/deploy/crds/cache_v1alpha1_memcached_crd.yaml`). -2. Delete the created project in `$GOPATH/src/github.com/example-inc/memcached-operator` +1. Delete the CRD (`kubectl delete -f $HOME/projects/example.com/memcached-operator/deploy/crds/cache_v1alpha1_memcached_crd.yaml`). +2. Delete the created project in `$HOME/projects/example.com/memcached-operator` 3. Delete the namespaces that the tests run in, which also deletes any resources created within the namespaces. The namespaces start with `memcached-memcached-group` or `main` and are appended with a unix timestamp (seconds since Jan 1 1970). The kubectl command can be used to delete namespaces: `kubectl delete namespace $NAMESPACE`. [travis]: ./travis-build.md diff --git a/doc/sdk-cli-reference.md b/doc/sdk-cli-reference.md index 080ba137e27..ddf281fe73a 100644 --- a/doc/sdk-cli-reference.md +++ b/doc/sdk-cli-reference.md @@ -270,8 +270,8 @@ Scaffolds a new operator project. #### Go project ```console -$ mkdir $GOPATH/src/github.com/example.com/ -$ cd $GOPATH/src/github.com/example.com/ +$ mkdir $HOME/projects/example.com/ +$ cd $HOME/projects/example.com/ $ operator-sdk new app-operator ``` diff --git a/doc/user-guide.md b/doc/user-guide.md index bd899e3b100..617d2068e23 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -23,8 +23,8 @@ Follow the steps in the [installation guide][install_guide] to learn how to inst Use the CLI to create a new memcached-operator project: ```sh -$ mkdir -p $GOPATH/src/github.com/example-inc/ -$ cd $GOPATH/src/github.com/example-inc/ +$ mkdir -p $HOME/projects/example.com/ +$ cd $HOME/projects/example.com/ $ operator-sdk new memcached-operator $ cd memcached-operator ``` @@ -433,7 +433,7 @@ The following is a snippet from the controller file under `pkg/controller/memcac 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) @@ -444,7 +444,7 @@ func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Res // TODO(user): Add the cleanup steps that the operator needs to do before the CR can be deleted // Update finalizer to allow delete CR memcached.SetFinalizers(nil) - + // Update CR err := r.client.Update(context.TODO(), memcached) if err != nil { @@ -452,22 +452,22 @@ func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Res } return reconcile.Result{}, nil } - + // Add finalizer for this CR if err := r.addFinalizer(reqLogger, instance); err != nil { return reconcile.Result{}, err } ... - + return reconcile.Result{}, nil } - + //addFinalizer will add this attribute to the Memcached CR func (r *ReconcileMemcached) addFinalizer(reqLogger logr.Logger, m *cachev1alpha1.Memcached) error { if len(m.GetFinalizers()) < 1 && m.GetDeletionTimestamp() == nil { reqLogger.Info("Adding Finalizer for the Memcached") m.SetFinalizers([]string{"finalizer.cache.example.com"}) - + // Update CR err := r.client.Update(context.TODO(), m) if err != nil { From 281f0c943756e46e109be31f2bc7cebb450df018 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 16:56:26 -0700 Subject: [PATCH 08/35] do not rely on /home/estroz/go in scripts, CLI, and tests --- cmd/operator-sdk/add/crd.go | 2 +- cmd/operator-sdk/build/cmd.go | 5 +++-- cmd/operator-sdk/new/cmd.go | 6 +++--- hack/image/build-ansible-image.sh | 2 +- hack/image/build-helm-image.sh | 2 +- hack/image/build-scorecard-proxy-image.sh | 6 +++++- hack/lib/test_lib.sh | 13 +++---------- hack/tests/e2e-ansible-molecule.sh | 2 +- hack/tests/e2e-ansible.sh | 2 +- hack/tests/e2e-helm.sh | 2 +- test/e2e/memcached_test.go | 13 ++++--------- 11 files changed, 24 insertions(+), 31 deletions(-) diff --git a/cmd/operator-sdk/add/crd.go b/cmd/operator-sdk/add/crd.go index aa090f55bcb..3b5b6678bd8 100644 --- a/cmd/operator-sdk/add/crd.go +++ b/cmd/operator-sdk/add/crd.go @@ -118,7 +118,7 @@ func verifyCRDFlags() error { return nil } -// verifyCRDDeployPath checks if the path /deploy sub-directory is exists, and that is rooted under $GOPATH +// verifyCRDDeployPath checks if the /deploy directory exists. func verifyCRDDeployPath() error { wd, err := os.Getwd() if err != nil { diff --git a/cmd/operator-sdk/build/cmd.go b/cmd/operator-sdk/build/cmd.go index 427c465d492..66c317aae07 100644 --- a/cmd/operator-sdk/build/cmd.go +++ b/cmd/operator-sdk/build/cmd.go @@ -92,7 +92,6 @@ func buildFunc(cmd *cobra.Command, args []string) error { goBuildEnv = append(goBuildEnv, "CGO_ENABLED=0") } - goTrimFlags := []string{"-gcflags", "all=-trimpath=${GOPATH}", "-asmflags", "all=-trimpath=${GOPATH}"} absProjectPath := projutil.MustGetwd() projectName := filepath.Base(absProjectPath) @@ -101,10 +100,12 @@ func buildFunc(cmd *cobra.Command, args []string) error { opts := projutil.GoCmdOptions{ BinName: filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName), PackagePath: filepath.Join(projutil.GetGoPkg(), scaffold.ManagerDir), - Args: goTrimFlags, Env: goBuildEnv, GoMod: projutil.IsDepManagerGoMod(), } + if _, ok := os.LookupEnv(projutil.GoPathEnv); ok { + opts.Args = []string{"-gcflags", "all=-trimpath=${GOPATH}", "-asmflags", "all=-trimpath=${GOPATH}"} + } if err := projutil.GoBuild(opts); err != nil { return fmt.Errorf("failed to build operator binary: (%v)", err) } diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index 31edd88d616..ce21120c511 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -43,10 +43,10 @@ generates a default directory layout based on the input . is the project name of the new operator. (e.g app-operator) For example: - $ mkdir $GOPATH/src/github.com/example.com/ - $ cd $GOPATH/src/github.com/example.com/ + $ mkdir $HOME/projects/example.com/ + $ cd $HOME/projects/example.com/ $ operator-sdk new app-operator -generates a skeletal app-operator application in $GOPATH/src/github.com/example.com/app-operator. +generates a skeletal app-operator application in $HOME/projects/example.com/app-operator. `, RunE: newFunc, } diff --git a/hack/image/build-ansible-image.sh b/hack/image/build-ansible-image.sh index d134245ff46..1e9813e4877 100755 --- a/hack/image/build-ansible-image.sh +++ b/hack/image/build-ansible-image.sh @@ -5,7 +5,7 @@ set -eux source hack/lib/test_lib.sh ROOTDIR="$(pwd)" -GOTMP="$(mktemp -d -p $GOPATH/src)" +GOTMP="$(mktemp -d)" trap_add 'rm -rf $GOTMP' EXIT BASEIMAGEDIR="$GOTMP/ansible-operator" mkdir -p "$BASEIMAGEDIR" diff --git a/hack/image/build-helm-image.sh b/hack/image/build-helm-image.sh index 80781618a12..81ad05d6b84 100755 --- a/hack/image/build-helm-image.sh +++ b/hack/image/build-helm-image.sh @@ -5,7 +5,7 @@ set -eux source hack/lib/test_lib.sh ROOTDIR="$(pwd)" -GOTMP="$(mktemp -d -p $GOPATH/src)" +GOTMP="$(mktemp -d)" trap_add 'rm -rf $GOTMP' EXIT BASEIMAGEDIR="$GOTMP/helm-operator" mkdir -p "$BASEIMAGEDIR" diff --git a/hack/image/build-scorecard-proxy-image.sh b/hack/image/build-scorecard-proxy-image.sh index 7f9f0cd70e7..b1dd1f808ef 100755 --- a/hack/image/build-scorecard-proxy-image.sh +++ b/hack/image/build-scorecard-proxy-image.sh @@ -3,7 +3,11 @@ set -eux # build operator binary and base image -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -gcflags "all=-trimpath=${GOPATH}" -asmflags "all=-trimpath=${GOPATH}" -o images/scorecard-proxy/scorecard-proxy images/scorecard-proxy/cmd/proxy/main.go +GCFLAGS= +if [[ -n "$GOPATH" ]]; then + GCFLAGS="-gcflags \"all=-trimpath=${GOPATH}\" -asmflags \"all=-trimpath=${GOPATH}\"" +fi +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $GCFLAGS -o images/scorecard-proxy/scorecard-proxy images/scorecard-proxy/cmd/proxy/main.go pushd images/scorecard-proxy docker build -t "$1" . popd diff --git a/hack/lib/test_lib.sh b/hack/lib/test_lib.sh index ee68081be8d..a9c7e35a565 100644 --- a/hack/lib/test_lib.sh +++ b/hack/lib/test_lib.sh @@ -2,20 +2,13 @@ source hack/lib/common.sh -function listPkgs() { - go list ./cmd/... ./pkg/... ./test/... | grep -v generated +function listPkgDirs() { + go list -f '{{.Dir}}' ./cmd/... ./pkg/... ./test/... ./internal/... | grep -v generated } function listFiles() { - # make it work with composed gopath - for gopath in ${GOPATH//:/ }; do - if [[ "$(pwd)" =~ "$gopath" ]]; then - GOPATH="$gopath" - break - fi - done # pipeline is much faster than for loop - listPkgs | xargs -I {} find "${GOPATH}/src/{}" -name '*.go' | grep -v generated + listPkgDirs | xargs -I {} find {} -name '*.go' | grep -v generated } #=================================================================== diff --git a/hack/tests/e2e-ansible-molecule.sh b/hack/tests/e2e-ansible-molecule.sh index 2261fb4d664..d4cfd4cfb60 100755 --- a/hack/tests/e2e-ansible-molecule.sh +++ b/hack/tests/e2e-ansible-molecule.sh @@ -5,7 +5,7 @@ source hack/lib/test_lib.sh set -eux ROOTDIR="$(pwd)" -GOTMP="$(mktemp -d -p $GOPATH/src)" +GOTMP="$(mktemp -d)" trap_add 'rm -rf $GOTMP' EXIT # Needs to be from source until 2.20 comes out pip install --user git+https://github.com/ansible/molecule.git diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index 2f39dabb3a0..d162461e386 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -9,7 +9,7 @@ go test -count=1 ./pkg/ansible/proxy/... DEST_IMAGE="quay.io/example/memcached-operator:v0.0.2" ROOTDIR="$(pwd)" -GOTMP="$(mktemp -d -p $GOPATH/src)" +GOTMP="$(mktemp -d)" trap_add 'rm -rf $GOTMP' EXIT deploy_operator() { diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index d1a8332a735..8a13b18f067 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -6,7 +6,7 @@ set -eux DEST_IMAGE="quay.io/example/nginx-operator:v0.0.2" ROOTDIR="$(pwd)" -GOTMP="$(mktemp -d -p $GOPATH/src)" +GOTMP="$(mktemp -d)" trap_add 'rm -rf $GOTMP' EXIT deploy_operator() { diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 3660fc27f2e..0a976ef9281 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -29,7 +29,6 @@ import ( "github.com/operator-framework/operator-sdk/internal/pkg/scaffold" "github.com/operator-framework/operator-sdk/internal/util/fileutil" - "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/internal/util/yamlutil" framework "github.com/operator-framework/operator-sdk/pkg/test" "github.com/operator-framework/operator-sdk/pkg/test/e2eutil" @@ -60,16 +59,12 @@ func TestMemcached(t *testing.T) { // get global framework variables ctx := framework.NewTestCtx(t) defer ctx.Cleanup() - gopath, ok := os.LookupEnv(projutil.GoPathEnv) - if !ok || gopath == "" { - t.Fatalf("$GOPATH not set") - } - cd, err := os.Getwd() + sdkDir, err := os.Getwd() if err != nil { t.Fatal(err) } defer func() { - if err := os.Chdir(cd); err != nil { + if err := os.Chdir(sdkDir); err != nil { t.Errorf("Failed to change back to original working directory: (%v)", err) } }() @@ -79,7 +74,7 @@ func TestMemcached(t *testing.T) { } // Setup - absProjectPath, err := ioutil.TempDir(filepath.Join(gopath, "src"), "tmp.") + absProjectPath, err := ioutil.TempDir("", "tmp.") if err != nil { t.Fatal(err) } @@ -105,7 +100,7 @@ func TestMemcached(t *testing.T) { } sdkRepo := "github.com/operator-framework/operator-sdk" - localSDKPath := filepath.Join(gopath, "src", sdkRepo) + localSDKPath := sdkDir replace := getGoModReplace(t, localSDKPath) if replace.repo != sdkRepo { From bbc2c693777ca1b34662744af61c55d57201ff6f Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 17 May 2019 15:09:31 -0700 Subject: [PATCH 09/35] test/e2e/main_test.go: use testArgs struct for test args; add local-repo flag to specify the local repo path if the SDK is not in $GOPATH/src/github.com/operator-framework/operator-sdk test/e2e/memcached_test.go: remove any 'replace' directives for the SDK repo in memcached-operator's go.mod file before adding one for the e2e test --- test/e2e/main_test.go | 10 ++++++++-- test/e2e/memcached_test.go | 32 +++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 863c6bb6bc6..98e4908e444 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -21,9 +21,15 @@ import ( f "github.com/operator-framework/operator-sdk/pkg/test" ) -var e2eImageName *string +type testArgs struct { + e2eImageName *string + localRepo *string +} + +var args = &testArgs{} func TestMain(m *testing.M) { - e2eImageName = flag.String("image", "", "operator image name : used to push the image, defaults to none (builds image to local docker repo)") + args.e2eImageName = flag.String("image", "", "operator image name : used to push the image, defaults to none (builds image to local docker repo)") + args.localRepo = flag.String("local-repo", "", "Path to local SDK repository being tested. Only use when running e2e tests locally") f.MainEntry(m) } diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 3660fc27f2e..00d395fcf86 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -64,12 +64,12 @@ func TestMemcached(t *testing.T) { if !ok || gopath == "" { t.Fatalf("$GOPATH not set") } - cd, err := os.Getwd() + sdkDir, err := os.Getwd() if err != nil { t.Fatal(err) } defer func() { - if err := os.Chdir(cd); err != nil { + if err := os.Chdir(sdkDir); err != nil { t.Errorf("Failed to change back to original working directory: (%v)", err) } }() @@ -79,7 +79,7 @@ func TestMemcached(t *testing.T) { } // Setup - absProjectPath, err := ioutil.TempDir(filepath.Join(gopath, "src"), "tmp.") + absProjectPath, err := ioutil.TempDir(filepath.Join(gopath, "src"), "sdktmp.") if err != nil { t.Fatal(err) } @@ -105,7 +105,10 @@ func TestMemcached(t *testing.T) { } sdkRepo := "github.com/operator-framework/operator-sdk" - localSDKPath := filepath.Join(gopath, "src", sdkRepo) + localSDKPath := *args.localRepo + if localSDKPath == "" { + localSDKPath = sdkDir + } replace := getGoModReplace(t, localSDKPath) if replace.repo != sdkRepo { @@ -315,6 +318,16 @@ func writeGoModReplace(t *testing.T, repo, path, sha string) { if err != nil { t.Fatalf("Failed to parse go.mod: %v", err) } + // Remove an existing replace line for the SDK if one exists. This is + // necessary for release PR's, which contain a replace directive for + // new SDK versions. + for _, r := range modFile.Replace { + if r.Old.Path == repo { + if err := modFile.DropReplace(repo, r.Old.Version); err != nil { + t.Fatalf(`Failed to remove "%s": %v`, modBytes[r.Syntax.Start.Byte:r.Syntax.End.Byte], err) + } + } + } if err = modFile.AddReplace(repo, "", path, sha); err != nil { s := "" if sha != "" { @@ -322,6 +335,7 @@ func writeGoModReplace(t *testing.T, repo, path, sha string) { } t.Fatalf(`Failed to add "replace %s => %s%s: %v"`, repo, path, s, err) } + modFile.Cleanup() if modBytes, err = modFile.Format(); err != nil { t.Fatalf("Failed to format go.mod: %v", err) } @@ -542,9 +556,9 @@ func MemcachedCluster(t *testing.T) { if err != nil { t.Fatalf("Could not read deploy/operator.yaml: %v", err) } - local := *e2eImageName == "" + local := *args.e2eImageName == "" if local { - *e2eImageName = "quay.io/example/memcached-operator:v0.0.1" + *args.e2eImageName = "quay.io/example/memcached-operator:v0.0.1" if err != nil { t.Fatal(err) } @@ -554,20 +568,20 @@ func MemcachedCluster(t *testing.T) { t.Fatal(err) } } - operatorYAML = bytes.Replace(operatorYAML, []byte("REPLACE_IMAGE"), []byte(*e2eImageName), 1) + operatorYAML = bytes.Replace(operatorYAML, []byte("REPLACE_IMAGE"), []byte(*args.e2eImageName), 1) err = ioutil.WriteFile("deploy/operator.yaml", operatorYAML, os.FileMode(0644)) if err != nil { t.Fatalf("Failed to write deploy/operator.yaml: %v", err) } t.Log("Building operator docker image") - cmdOut, err := exec.Command("operator-sdk", "build", *e2eImageName).CombinedOutput() + cmdOut, err := exec.Command("operator-sdk", "build", *args.e2eImageName).CombinedOutput() if err != nil { t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } if !local { t.Log("Pushing docker image to repo") - cmdOut, err = exec.Command("docker", "push", *e2eImageName).CombinedOutput() + cmdOut, err = exec.Command("docker", "push", *args.e2eImageName).CombinedOutput() if err != nil { t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } From d5be9a3cbb3080d48ae90f511a48c1d06768f898 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 16:17:15 -0700 Subject: [PATCH 10/35] hack/tests/e2e-{ansible,helm}.sh: remove SDK replace lines in go.mod before adding one --- hack/tests/e2e-ansible.sh | 14 +++++++++++--- hack/tests/e2e-helm.sh | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index 2f39dabb3a0..534564da22d 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -133,6 +133,13 @@ then exit 1 fi +# Remove any "replace" lines for the SDK repo before vendoring in case this is +# a release PR and the tag doesn't exist yet. This must be done without using +# "go mod edit", which first parses go.mod and will error if it doesn't find +# a tag/version. +SDK_REPO="github.com/operator-framework/operator-sdk" +sed -E -i 's|^(replace)?.*'"$SDK_REPO"'.*=>.*$||g' go.mod + # Right now, SDK projects still need a vendor directory, so run `go mod vendor` # to pull down the deps specified by the scaffolded `go.mod` file. go mod vendor @@ -141,9 +148,10 @@ go mod vendor # happy, the directory needs a `go.mod` file that specifies the module name, # so we need this temporary hack until we update the SDK repo itself to use # go modules. -echo "module github.com/operator-framework/operator-sdk" > $ROOTDIR/go.mod -trap_add 'rm $ROOTDIR/go.mod' EXIT -go mod edit -replace=github.com/operator-framework/operator-sdk=$ROOTDIR +echo "module ${SDK_REPO}" > "${ROOTDIR}/go.mod" +trap_add "rm ${ROOTDIR}/go.mod" EXIT +go mod edit -replace="${SDK_REPO}=$ROOTDIR" +go mod vendor operator-sdk build "$DEST_IMAGE" diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index df25374efbb..ac4a2dd1496 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -123,6 +123,13 @@ then exit 1 fi +# Remove any "replace" lines for the SDK repo before vendoring in case this is +# a release PR and the tag doesn't exist yet. This must be done without using +# "go mod edit", which first parses go.mod and will error if it doesn't find +# a tag/version. +SDK_REPO="github.com/operator-framework/operator-sdk" +sed -E -i 's|^(replace)?.*'"$SDK_REPO"'.*=>.*$||g' go.mod + # Right now, SDK projects still need a vendor directory, so run `go mod vendor` # to pull down the deps specified by the scaffolded `go.mod` file. go mod vendor @@ -131,9 +138,10 @@ go mod vendor # happy, the directory needs a `go.mod` file that specifies the module name, # so we need this temporary hack until we update the SDK repo itself to use # go modules. -echo "module github.com/operator-framework/operator-sdk" > $ROOTDIR/go.mod -trap_add 'rm $ROOTDIR/go.mod' EXIT -go mod edit -replace=github.com/operator-framework/operator-sdk=$ROOTDIR +echo "module ${SDK_REPO}" > "${ROOTDIR}/go.mod" +trap_add "rm ${ROOTDIR}/go.mod" EXIT +go mod edit -replace="${SDK_REPO}=$ROOTDIR" +go mod vendor operator-sdk build "$DEST_IMAGE" From fdae734e9ab81f036caf9e6b4f5198d475808e5d Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 22 May 2019 17:59:49 -0700 Subject: [PATCH 11/35] use --repo for 'operator-sdk new' in e2e --- hack/tests/e2e-ansible-molecule.sh | 7 ++++++- hack/tests/e2e-ansible.sh | 8 ++++++-- hack/tests/e2e-helm.sh | 9 +++++++-- test/e2e/memcached_test.go | 6 +++--- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/hack/tests/e2e-ansible-molecule.sh b/hack/tests/e2e-ansible-molecule.sh index d4cfd4cfb60..84edfe1bcda 100755 --- a/hack/tests/e2e-ansible-molecule.sh +++ b/hack/tests/e2e-ansible-molecule.sh @@ -26,7 +26,12 @@ remove_prereqs() { } pushd "$GOTMP" -operator-sdk new memcached-operator --api-version=ansible.example.com/v1alpha1 --kind=Memcached --type=ansible --generate-playbook +operator-sdk new memcached-operator \ + --api-version=ansible.example.com/v1alpha1 \ + --kind=Memcached \ + --type=ansible \ + --generate-playbook \ + --repo=github.com/example-inc/memcached-operator cp "$ROOTDIR/test/ansible-memcached/tasks.yml" memcached-operator/roles/memcached/tasks/main.yml cp "$ROOTDIR/test/ansible-memcached/defaults.yml" memcached-operator/roles/memcached/defaults/main.yml cp "$ROOTDIR/test/ansible-memcached/asserts.yml" memcached-operator/molecule/default/asserts.yml diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index d162461e386..f174ef7b5d3 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -96,7 +96,11 @@ if which oc 2>/dev/null; then oc project default; fi # create and build the operator pushd "$GOTMP" -operator-sdk new memcached-operator --api-version=ansible.example.com/v1alpha1 --kind=Memcached --type=ansible +operator-sdk new memcached-operator \ + --api-version=ansible.example.com/v1alpha1 \ + --kind=Memcached \ + --type=ansible \ + --repo=github.com/example-inc/memcached-operator cp "$ROOTDIR/test/ansible-memcached/tasks.yml" memcached-operator/roles/memcached/tasks/main.yml cp "$ROOTDIR/test/ansible-memcached/defaults.yml" memcached-operator/roles/memcached/defaults/main.yml cp -a "$ROOTDIR/test/ansible-memcached/memfin" memcached-operator/roles/ @@ -125,7 +129,7 @@ echo "### Now testing migrate to hybrid operator" echo "###" export GO111MODULE=on -operator-sdk migrate +operator-sdk migrate --repo=github.com/example-inc/memcached-operator if [[ ! -e build/Dockerfile.sdkold ]]; then diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index 8a13b18f067..b7978cd8940 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -94,7 +94,12 @@ fi # create and build the operator pushd "$GOTMP" -log=$(operator-sdk new nginx-operator --api-version=helm.example.com/v1alpha1 --kind=Nginx --type=helm 2>&1) +log=$(operator-sdk new nginx-operator \ + --api-version=helm.example.com/v1alpha1 \ + --kind=Nginx \ + --type=helm \ + --repo=github.com/example-inc/nginx-operator \ + 2>&1) echo $log if echo $log | grep -q "failed to generate RBAC rules"; then echo FAIL expected successful generation of RBAC rules @@ -120,7 +125,7 @@ echo "### Now testing migrate to hybrid operator" echo "###" export GO111MODULE=on -operator-sdk migrate +operator-sdk migrate --repo=github.com/example-inc/nginx-operator if [[ ! -e build/Dockerfile.sdkold ]]; then diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 0a976ef9281..a481b55fdb3 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -88,9 +88,11 @@ func TestMemcached(t *testing.T) { } t.Log("Creating new operator project") + sdkRepo := "github.com/operator-framework/operator-sdk" cmdOut, err := exec.Command("operator-sdk", "new", - operatorName).CombinedOutput() + operatorName, + "--repo", sdkRepo).CombinedOutput() if err != nil { t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } @@ -99,9 +101,7 @@ func TestMemcached(t *testing.T) { t.Fatalf("Failed to change to %s directory: (%v)", operatorName, err) } - sdkRepo := "github.com/operator-framework/operator-sdk" localSDKPath := sdkDir - replace := getGoModReplace(t, localSDKPath) if replace.repo != sdkRepo { if replace.isLocal { From 81dc7e7f51546889e72e4c576480249a0cd78be0 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 23 May 2019 14:02:50 -0700 Subject: [PATCH 12/35] dep manager check for all commands used in project root --- cmd/operator-sdk/main.go | 76 ++++++++++++++++++++++++-- cmd/operator-sdk/migrate/cmd.go | 18 +----- cmd/operator-sdk/new/cmd.go | 18 +----- internal/util/projutil/project_util.go | 22 ++++++-- 4 files changed, 91 insertions(+), 43 deletions(-) diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index c9737420fcc..6d034f8af8c 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -15,9 +15,11 @@ package main import ( - "github.com/operator-framework/operator-sdk/internal/util/projutil" + "fmt" "os" + "github.com/operator-framework/operator-sdk/internal/util/projutil" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that `run` and `up local` can make use of them. "github.com/operator-framework/operator-sdk/cmd/operator-sdk/add" @@ -47,14 +49,15 @@ func main() { Short: "An SDK for building operators with ease", PersistentPreRun: func(cmd *cobra.Command, args []string) { if viper.GetBool(flags.VerboseOpt) { - err := projutil.SetGoVerbose() - if err != nil { - log.Errorf("Could not set GOFLAGS: (%v)", err) - return + if err := projutil.SetGoVerbose(); err != nil { + log.Fatalf("Could not set GOFLAGS: (%v)", err) } log.SetLevel(log.DebugLevel) log.Debug("Debug logging is set") } + if err := checkDepManagerForCmd(cmd); err != nil { + log.Fatal(err) + } }, } @@ -81,3 +84,66 @@ func main() { os.Exit(1) } } + +func checkDepManagerForCmd(cmd *cobra.Command) (err error) { + // Do not perform this check if wd is not in the project root, + // as some sub-commands might not require project root. + if err := projutil.CheckProjectRoot(); err != nil { + return nil + } + + var dm projutil.DepManagerType + switch cmd.Name() { + case "new", "migrate": + // Do not perform this check if the new project is non-Go, as they do not + // have (Go) dep managers. + if cmd.Name() == "new" { + projType, err := cmd.Flags().GetString("type") + if err != nil { + return err + } + if projType != "go" { + return nil + } + } + // "new" and "migrate" commands are for projects that establish witch + // dep manager to use. "new" should not be called if we're in the project + // root but could be, so check it anyway. + dmStr, err := cmd.Flags().GetString("dep-manager") + if err != nil { + return err + } + dm = projutil.DepManagerType(dmStr) + default: + // Do not perform this check if the project is non-Go, as they do not + // have (Go) dep managers. + if !projutil.IsOperatorGo() { + return nil + } + if dm, err = projutil.GetDepManagerType(); err != nil { + return err + } + } + + switch dm { + case projutil.DepManagerGoMod: + goModOn, err := projutil.GoModOn() + if err != nil { + return err + } + if !goModOn { + return fmt.Errorf(`depedency manger "modules" requires wd be in $GOPATH/src` + + ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="on", "auto", or unset`) + } + case projutil.DepManagerDep: + inGopathSrc, err := projutil.WdInGoPathSrc() + if err != nil { + return err + } + if !inGopathSrc { + return fmt.Errorf(`depedency manger "dep" requires wd be in $GOPATH/src`) + } + } + + return nil +} diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 5f58faa2f29..96ec06b4b79 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -86,22 +86,8 @@ func verifyFlags() error { if err != nil { return err } - if !inGopathSrc { - if depManager == string(projutil.DepManagerDep) { - return fmt.Errorf(`depedency manger "dep" selected but wd not in $GOPATH/src`) - } - if repo == "" && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) - } - } - - goModOn, err := projutil.GoModOn() - if err != nil { - return err - } - if !goModOn && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires wd be in $GOPATH/src` + - ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="auto" or unset`) + if !inGopathSrc && repo == "" && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) } return nil diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index ce21120c511..9a948abb302 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -408,22 +408,8 @@ func verifyFlags() error { if err != nil { return err } - if !inGopathSrc { - if depManager == string(projutil.DepManagerDep) { - return fmt.Errorf(`depedency manger "dep" selected but wd not in $GOPATH/src`) - } - if repo == "" && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) - } - } - - goModOn, err := projutil.GoModOn() - if err != nil { - return err - } - if !goModOn && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires wd be in $GOPATH/src` + - ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="auto" or unset`) + if !inGopathSrc && repo == "" && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) } return nil diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index cd7d9638a1a..ccc176b0f12 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -23,6 +23,7 @@ import ( "strings" homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" "github.com/rogpeppe/go-internal/modfile" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -102,24 +103,33 @@ func IsDepManagerGoMod() bool { return err == nil || os.IsExist(err) } -// MustInProjectRoot checks if the current dir is the project root and returns -// the current repo's import path, ex github.com/example-inc/app-operator +// MustInProjectRoot checks if the current dir is the project root, and exits +// if not. func MustInProjectRoot() { - // If the current directory has a "build/dockerfile", then it is safe to say + if err := CheckProjectRoot(); err != nil { + log.Fatal(err) + } +} + +// CheckProjectRoot checks if the current dir is the project root, and returns +// an error if not. +func CheckProjectRoot() error { + // If the current directory has a "build/Dockerfile", then it is safe to say // we are at the project root. if _, err := os.Stat(buildDockerfile); err != nil { if os.IsNotExist(err) { - log.Fatalf("Must run command in project root dir: project structure requires %s", buildDockerfile) + return fmt.Errorf("must run command in project root dir: project structure requires %s", buildDockerfile) } - log.Fatalf("Error while checking if current directory is the project root: (%v)", err) + return errors.Wrap(err, "error while checking if current directory is the project root") } + return nil } func CheckGoProjectCmd(cmd *cobra.Command) error { if IsOperatorGo() { return nil } - return fmt.Errorf("'%s' can only be run for Go operators; %s does not exist.", cmd.CommandPath(), mainFile) + return fmt.Errorf("command '%s' can only be run for Go operators; %s does not exist", cmd.CommandPath(), mainFile) } func MustGetwd() string { From dc6d2364e7851af83adc4eccf50b7be54d5ac55b Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 23 May 2019 14:16:49 -0700 Subject: [PATCH 13/35] revendor --- Gopkg.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 9fcb76b8fd7..362b0a374e1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1445,12 +1445,12 @@ version = "kubernetes-1.13.1" [[projects]] - branch = "master" digest = "1:9cf5396ad425691289c57c8ec922a0ab733b9ec8afdf22044056ee708250c05f" name = "k8s.io/code-generator" packages = ["cmd/deepcopy-gen/args"] pruneopts = "NUT" - revision = "639c964206c28ac3859cf36f212c24775616884a" + revision = "c2090bec4d9b1fb25de3812f868accc2bc9ecbae" + version = "kubernetes-1.13.1" [[projects]] branch = "master" From 4a0efa620cdbf95ef62184babe2120d7efa8549a Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 24 May 2019 11:44:12 -0400 Subject: [PATCH 14/35] use correct repo for new project; cp e2e code templates without replacing repo --- test/e2e/memcached_test.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index a481b55fdb3..78394329293 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -52,7 +52,9 @@ const ( timeout = time.Second * 120 cleanupRetryInterval = time.Second * 1 cleanupTimeout = time.Second * 10 + sdkRepo = "github.com/operator-framework/operator-sdk" operatorName = "memcached-operator" + testRepo = "github.com/example-inc/" + operatorName ) func TestMemcached(t *testing.T) { @@ -88,11 +90,10 @@ func TestMemcached(t *testing.T) { } t.Log("Creating new operator project") - sdkRepo := "github.com/operator-framework/operator-sdk" cmdOut, err := exec.Command("operator-sdk", "new", operatorName, - "--repo", sdkRepo).CombinedOutput() + "--repo", testRepo).CombinedOutput() if err != nil { t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } @@ -169,13 +170,9 @@ func TestMemcached(t *testing.T) { if err := os.MkdirAll(filepath.Dir(dst), fileutil.DefaultDirFileMode); err != nil { t.Fatalf("Could not create template destination directory: %s", err) } - srcTmpl, err := ioutil.ReadFile(src) + cmdOut, err = exec.Command("cp", src, dst).CombinedOutput() if err != nil { - t.Fatalf("Could not read template from %s: %s", src, err) - } - dstData := strings.Replace(string(srcTmpl), "github.com/example-inc", filepath.Base(absProjectPath), -1) - if err := ioutil.WriteFile(dst, []byte(dstData), fileutil.DefaultFileMode); err != nil { - t.Fatalf("Could not write template output to %s: %s", dst, err) + t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } } From 37d21eb2ae81324d228cb27f3e98cf0f69365b94 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 23 May 2019 15:14:53 -0700 Subject: [PATCH 15/35] get correct SDK dir in e2e --- test/e2e/memcached_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 00d395fcf86..6bcbe484cd3 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -64,22 +64,27 @@ func TestMemcached(t *testing.T) { if !ok || gopath == "" { t.Fatalf("$GOPATH not set") } - sdkDir, err := os.Getwd() + sdkTestE2EDir, err := os.Getwd() if err != nil { t.Fatal(err) } defer func() { - if err := os.Chdir(sdkDir); err != nil { + if err := os.Chdir(sdkTestE2EDir); err != nil { t.Errorf("Failed to change back to original working directory: (%v)", err) } }() + localSDKPath := *args.localRepo + if localSDKPath == "" { + // We're in ${sdk_repo}/test/e2e + localSDKPath = filepath.Dir(filepath.Dir(sdkTestE2EDir)) + } // For go commands in operator projects. if err = os.Setenv("GO111MODULE", "on"); err != nil { t.Fatal(err) } // Setup - absProjectPath, err := ioutil.TempDir(filepath.Join(gopath, "src"), "sdktmp.") + absProjectPath, err := ioutil.TempDir(filepath.Join(gopath, "src"), "tmp.") if err != nil { t.Fatal(err) } @@ -105,11 +110,6 @@ func TestMemcached(t *testing.T) { } sdkRepo := "github.com/operator-framework/operator-sdk" - localSDKPath := *args.localRepo - if localSDKPath == "" { - localSDKPath = sdkDir - } - replace := getGoModReplace(t, localSDKPath) if replace.repo != sdkRepo { if replace.isLocal { From 0ff9592c15f8ca01b03d3fb0003592fdf817d895 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 31 May 2019 14:47:25 -0400 Subject: [PATCH 16/35] fix comment spelling --- cmd/operator-sdk/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index 6d034f8af8c..dce619e6b18 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -106,7 +106,7 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { return nil } } - // "new" and "migrate" commands are for projects that establish witch + // "new" and "migrate" commands are for projects that establish which // dep manager to use. "new" should not be called if we're in the project // root but could be, so check it anyway. dmStr, err := cmd.Flags().GetString("dep-manager") From b6bfd1db82d4c1b68320142c02b36f7085234def Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 31 May 2019 14:53:55 -0400 Subject: [PATCH 17/35] internal/util/projutil: generalize project util functions for GOPATH and non-GOPATH scenarios --- internal/util/projutil/exec.go | 43 +++++++++----- internal/util/projutil/project_util.go | 81 ++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/internal/util/projutil/exec.go b/internal/util/projutil/exec.go index 1fde21f913a..bd2f6359b53 100644 --- a/internal/util/projutil/exec.go +++ b/internal/util/projutil/exec.go @@ -50,7 +50,7 @@ type GoCmdOptions struct { // Dir is the dir to run "go {cmd}" in; exec.Command.Dir is set to this value. Dir string // GoMod determines whether to set the "-mod=vendor" flag. - // If true, "go {cmd}" will use modules. + // If true and ./vendor/ exists, "go {cmd}" will use vendored modules. // If false, "go {cmd}" will not use go modules. This is the default. // This applies to build, clean, get, install, list, run, and test. GoMod bool @@ -70,34 +70,42 @@ const ( // GoBuild runs "go build" configured with opts. func GoBuild(opts GoCmdOptions) error { - return goCmd(goBuildCmd, opts) + return GoCmd(goBuildCmd, opts) } // GoTest runs "go test" configured with opts. func GoTest(opts GoTestOptions) error { - bargs, err := getGeneralArgs("test", opts.GoCmdOptions) + bargs, err := opts.getGeneralArgsWithCmd("test") if err != nil { return err } bargs = append(bargs, opts.TestBinaryArgs...) c := exec.Command("go", bargs...) - setCommandFields(c, opts.GoCmdOptions) + opts.setCmdFields(c) return ExecCmd(c) } -// goCmd runs "go cmd".. -func goCmd(cmd string, opts GoCmdOptions) error { - bargs, err := getGeneralArgs(cmd, opts) +// GoCmd runs "go cmd".. +func GoCmd(cmd string, opts GoCmdOptions) error { + bargs, err := opts.getGeneralArgsWithCmd(cmd) if err != nil { return err } c := exec.Command("go", bargs...) - setCommandFields(c, opts) + opts.setCmdFields(c) return ExecCmd(c) } -func getGeneralArgs(cmd string, opts GoCmdOptions) ([]string, error) { - bargs := []string{cmd} +func (opts GoCmdOptions) getGeneralArgsWithCmd(cmd string) ([]string, error) { + // Go subcommands with more than one child command must be passed as + // multiple arguments instead of a spaced string, ex. "go mod init". + bargs := []string{} + for _, c := range strings.Split(cmd, " ") { + if ct := strings.TrimSpace(c); ct != "" { + bargs = append(bargs, ct) + } + } + if opts.BinName != "" { bargs = append(bargs, "-o", opts.BinName) } @@ -106,13 +114,18 @@ func getGeneralArgs(cmd string, opts GoCmdOptions) ([]string, error) { if goModOn, err := GoModOn(); err != nil { return nil, err } else if goModOn { - bargs = append(bargs, "-mod=vendor") + if info, err := os.Stat("vendor"); err == nil && info.IsDir() { + bargs = append(bargs, "-mod=vendor") + } } } - return append(bargs, opts.PackagePath), nil + if opts.PackagePath != "" { + bargs = append(bargs, opts.PackagePath) + } + return bargs, nil } -func setCommandFields(c *exec.Cmd, opts GoCmdOptions) { +func (opts GoCmdOptions) setCmdFields(c *exec.Cmd) { if len(opts.Env) != 0 { c.Env = append(os.Environ(), opts.Env...) } @@ -137,14 +150,14 @@ func GoModOn() (bool, error) { if v == "on" { return true, nil } - inSrc, err := wdInGoPathSrc() + inSrc, err := WdInGoPathSrc() if err != nil { return false, err } return !inSrc && (!ok || v == "" || v == "auto"), nil } -func wdInGoPathSrc() (bool, error) { +func WdInGoPathSrc() (bool, error) { wd, err := os.Getwd() if err != nil { return false, err diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index e30c20ed82c..23af71adb60 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -16,12 +16,15 @@ package projutil import ( "fmt" + "io/ioutil" "os" "path/filepath" "regexp" "strings" homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" + "github.com/rogpeppe/go-internal/modfile" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -100,24 +103,33 @@ func IsDepManagerGoMod() bool { return err == nil || os.IsExist(err) } -// MustInProjectRoot checks if the current dir is the project root and returns -// the current repo's import path, ex github.com/example-inc/app-operator +// MustInProjectRoot checks if the current dir is the project root, and exits +// if not. func MustInProjectRoot() { - // If the current directory has a "build/dockerfile", then it is safe to say + if err := CheckProjectRoot(); err != nil { + log.Fatal(err) + } +} + +// CheckProjectRoot checks if the current dir is the project root, and returns +// an error if not. +func CheckProjectRoot() error { + // If the current directory has a "build/Dockerfile", then it is safe to say // we are at the project root. if _, err := os.Stat(buildDockerfile); err != nil { if os.IsNotExist(err) { - log.Fatalf("Must run command in project root dir: project structure requires %s", buildDockerfile) + return fmt.Errorf("must run command in project root dir: project structure requires %s", buildDockerfile) } - log.Fatalf("Error while checking if current directory is the project root: (%v)", err) + return errors.Wrap(err, "error while checking if current directory is the project root") } + return nil } func CheckGoProjectCmd(cmd *cobra.Command) error { if IsOperatorGo() { return nil } - return fmt.Errorf("'%s' can only be run for Go operators; %s does not exist.", cmd.CommandPath(), mainFile) + return fmt.Errorf("command '%s' can only be run for Go operators; %s does not exist", cmd.CommandPath(), mainFile) } func MustGetwd() string { @@ -140,7 +152,52 @@ func getHomeDir() (string, error) { // under $GOPATH and returns the current directory's import path, // e.g: "github.com/example-inc/app-operator" func CheckAndGetProjectGoPkg() string { - gopath := MustSetGopath(MustGetGopath()) + gopath := MustSetFirstGopath(MustGetGopath()) + return parseGoPkg(gopath) +} + +// GetGoPkg returns the current directory's import path by parsing it from +// wd if this project's repository path is rooted under $GOPATH/src, or +// from go.mod the project uses go modules to manage dependencies. +// +// Example: "github.com/example-inc/app-operator" +func GetGoPkg() string { + // Default to reading from go.mod, as it should usually have the (correct) + // package path, and no further processing need be done on it if so. + if _, err := os.Stat(goModFile); err == nil { + b, err := ioutil.ReadFile(goModFile) + if err != nil { + log.Fatalf("Read go.mod: %v", err) + } + mf, err := modfile.Parse(goModFile, b, nil) + if err != nil { + log.Fatalf("Parse go.mod: %v", err) + } + if mf.Module != nil && mf.Module.Mod.Path != "" { + return mf.Module.Mod.Path + } + } + + // Then try parsing package path from $GOPATH (set env or default). + goPath, ok := os.LookupEnv(GoPathEnv) + if !ok || goPath == "" { + hd, err := getHomeDir() + if err != nil { + log.Fatal(err) + } + goPath = filepath.Join(hd, "go", "src") + } else { + // MustSetFirstGopath is necessary here because the user has set GOPATH, + // which could be a path list. + goPath = MustSetFirstGopath(goPath) + } + if !strings.HasPrefix(MustGetwd(), goPath) { + log.Fatal("Could not determine project repository path: $GOPATH not set, wd in default $HOME/go/src, or wd does not contain a go.mod") + } + return parseGoPkg(goPath) +} + +func parseGoPkg(gopath string) string { goSrc := filepath.Join(gopath, SrcDir) wd := MustGetwd() pathedPkg := strings.Replace(wd, goSrc, "", 1) @@ -188,16 +245,16 @@ func MustGetGopath() string { return gopath } -// MustSetGopath sets GOPATH=currentGopath after processing a path list, -// if any, then returns the set path. If GOPATH cannot be set, MustSetGopath -// exits. -func MustSetGopath(currentGopath string) string { +// MustSetFirstGopath sets GOPATH to the first element of the path list in +// currentGopath, then returns the set path. +// If GOPATH cannot be set, MustSetFirstGopath exits. +func MustSetFirstGopath(currentGopath string) string { var ( newGopath string cwdInGopath bool wd = MustGetwd() ) - for _, newGopath = range strings.Split(currentGopath, ":") { + for _, newGopath = range filepath.SplitList(currentGopath) { if strings.HasPrefix(filepath.Dir(wd), newGopath) { cwdInGopath = true break From 566aa6c5b2a18c345e63046aaecb5ce1ca1111c7 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 11 Jun 2019 09:51:21 -0700 Subject: [PATCH 18/35] comment out SDK require before parsing --- test/e2e/memcached_test.go | 43 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 21f98c1faec..9d661cb6e26 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -100,7 +100,8 @@ func TestMemcached(t *testing.T) { t.Log("Creating new operator project") cmdOut, err := exec.Command("operator-sdk", "new", - operatorName).CombinedOutput() + operatorName, + "--skip-validation").CombinedOutput() if err != nil { t.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut)) } @@ -128,7 +129,25 @@ func TestMemcached(t *testing.T) { } }() } - writeGoModReplace(t, sdkRepo, replace.repo, replace.ref) + modBytes, err := ioutil.ReadFile("go.mod") + if err != nil { + t.Fatalf("Failed to read go.mod: %v", err) + } + // Comment out SDK repo require line so we can parse the modfile. + sdkRequire := []byte(fmt.Sprintf("%s master", sdkRepo)) + sdkRequireCommented := []byte(fmt.Sprintf("// %s", string(sdkRequire))) + modBytes = bytes.Replace(modBytes, sdkRequire, sdkRequireCommented, 1) + modBytes, err = insertGoModReplace(t, modBytes, sdkRepo, replace.repo, replace.ref) + if err != nil { + t.Fatalf("Failed to insert replace: %v", err) + } + // Uncomment before writing. + modBytes = bytes.Replace(modBytes, sdkRequireCommented, sdkRequire, 1) + err = ioutil.WriteFile("go.mod", modBytes, fileutil.DefaultFileMode) + if err != nil { + t.Fatalf("Failed to write updated go.mod: %v", err) + } + t.Logf("go.mod: %v", string(modBytes)) } cmdOut, err = exec.Command("go", "build", "./...").CombinedOutput() @@ -309,14 +328,10 @@ func getGoModReplace(t *testing.T, localSDKPath string) goModReplace { } } -func writeGoModReplace(t *testing.T, repo, path, sha string) { - modBytes, err := ioutil.ReadFile("go.mod") - if err != nil { - t.Fatalf("Failed to read go.mod: %v", err) - } +func insertGoModReplace(t *testing.T, modBytes []byte, repo, path, sha string) ([]byte, error) { modFile, err := modfile.Parse("go.mod", modBytes, nil) if err != nil { - t.Fatalf("Failed to parse go.mod: %v", err) + return nil, fmt.Errorf("failed to parse go.mod: %v", err) } // Remove an existing replace line for the SDK if one exists. This is // necessary for release PR's, which contain a replace directive for @@ -324,7 +339,7 @@ func writeGoModReplace(t *testing.T, repo, path, sha string) { for _, r := range modFile.Replace { if r.Old.Path == repo { if err := modFile.DropReplace(repo, r.Old.Version); err != nil { - t.Fatalf(`Failed to remove "%s": %v`, modBytes[r.Syntax.Start.Byte:r.Syntax.End.Byte], err) + return nil, fmt.Errorf(`failed to remove "%s": %v`, modBytes[r.Syntax.Start.Byte:r.Syntax.End.Byte], err) } } } @@ -333,17 +348,13 @@ func writeGoModReplace(t *testing.T, repo, path, sha string) { if sha != "" { s = " " + sha } - t.Fatalf(`Failed to add "replace %s => %s%s: %v"`, repo, path, s, err) + return nil, fmt.Errorf(`failed to add "replace %s => %s%s: %v"`, repo, path, s, err) } modFile.Cleanup() if modBytes, err = modFile.Format(); err != nil { - t.Fatalf("Failed to format go.mod: %v", err) - } - err = ioutil.WriteFile("go.mod", modBytes, fileutil.DefaultFileMode) - if err != nil { - t.Fatalf("Failed to write updated go.mod: %v", err) + return nil, fmt.Errorf("failed to format go.mod: %v", err) } - t.Logf("go.mod: %v", string(modBytes)) + return modBytes, nil } func memcachedLeaderTest(t *testing.T, f *framework.Framework, ctx *framework.TestCtx) error { From 370ff16aa489e1ef51829f0af175018e79f55ef6 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 11 Jun 2019 10:21:06 -0700 Subject: [PATCH 19/35] use regex to replace all references to the SDK repo --- hack/tests/e2e-ansible.sh | 10 +++++----- hack/tests/e2e-helm.sh | 10 +++++----- test/e2e/memcached_test.go | 20 ++++---------------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index 36c13b8162d..dbd284465a2 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -133,12 +133,12 @@ then exit 1 fi -# Remove any "replace" lines for the SDK repo before vendoring in case this is -# a release PR and the tag doesn't exist yet. This must be done without using -# "go mod edit", which first parses go.mod and will error if it doesn't find -# a tag/version. +# Remove any "replace" and "require" lines for the SDK repo before vendoring +# in case this is a release PR and the tag doesn't exist yet. This must be +# done without using "go mod edit", which first parses go.mod and will error +# if it doesn't find a tag/version/package. SDK_REPO="github.com/operator-framework/operator-sdk" -sed -E -i 's|^(replace)?.*'"$SDK_REPO"'.*=>.*$||g' go.mod +sed -E -i 's|^.*'"$SDK_REPO"'.*$||g' go.mod # Run `go build ./...` to pull down the deps specified by the scaffolded # `go.mod` file and verify dependencies build correctly. diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index b9b880a9b71..773db5799bb 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -142,12 +142,12 @@ then exit 1 fi -# Remove any "replace" lines for the SDK repo before vendoring in case this is -# a release PR and the tag doesn't exist yet. This must be done without using -# "go mod edit", which first parses go.mod and will error if it doesn't find -# a tag/version. +# Remove any "replace" and "require" lines for the SDK repo before vendoring +# in case this is a release PR and the tag doesn't exist yet. This must be +# done without using "go mod edit", which first parses go.mod and will error +# if it doesn't find a tag/version/package. SDK_REPO="github.com/operator-framework/operator-sdk" -sed -E -i 's|^(replace)?.*'"$SDK_REPO"'.*=>.*$||g' go.mod +sed -E -i 's|^.*'"$SDK_REPO"'.*$||g' go.mod # Run `go build ./...` to pull down the deps specified by the scaffolded # `go.mod` file and verify dependencies build correctly. diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 9d661cb6e26..6984a277381 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -23,6 +23,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "testing" "time" @@ -133,16 +134,13 @@ func TestMemcached(t *testing.T) { if err != nil { t.Fatalf("Failed to read go.mod: %v", err) } - // Comment out SDK repo require line so we can parse the modfile. - sdkRequire := []byte(fmt.Sprintf("%s master", sdkRepo)) - sdkRequireCommented := []byte(fmt.Sprintf("// %s", string(sdkRequire))) - modBytes = bytes.Replace(modBytes, sdkRequire, sdkRequireCommented, 1) + // Remove SDK repo dependency lines so we can parse the modfile. + sdkRe := regexp.MustCompile(`.*github\.com/operator-framework/operator-sdk.*`) + modBytes = sdkRe.ReplaceAll(modBytes, nil) modBytes, err = insertGoModReplace(t, modBytes, sdkRepo, replace.repo, replace.ref) if err != nil { t.Fatalf("Failed to insert replace: %v", err) } - // Uncomment before writing. - modBytes = bytes.Replace(modBytes, sdkRequireCommented, sdkRequire, 1) err = ioutil.WriteFile("go.mod", modBytes, fileutil.DefaultFileMode) if err != nil { t.Fatalf("Failed to write updated go.mod: %v", err) @@ -333,16 +331,6 @@ func insertGoModReplace(t *testing.T, modBytes []byte, repo, path, sha string) ( if err != nil { return nil, fmt.Errorf("failed to parse go.mod: %v", err) } - // Remove an existing replace line for the SDK if one exists. This is - // necessary for release PR's, which contain a replace directive for - // new SDK versions. - for _, r := range modFile.Replace { - if r.Old.Path == repo { - if err := modFile.DropReplace(repo, r.Old.Version); err != nil { - return nil, fmt.Errorf(`failed to remove "%s": %v`, modBytes[r.Syntax.Start.Byte:r.Syntax.End.Byte], err) - } - } - } if err = modFile.AddReplace(repo, "", path, sha); err != nil { s := "" if sha != "" { From e23b6fa8d8885aaa3da773331dd32005b90d2720 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 11 Jun 2019 10:28:36 -0700 Subject: [PATCH 20/35] rename GOPATH setter util function --- internal/util/projutil/project_util.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 23af71adb60..1f0b93ab3dc 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -152,7 +152,7 @@ func getHomeDir() (string, error) { // under $GOPATH and returns the current directory's import path, // e.g: "github.com/example-inc/app-operator" func CheckAndGetProjectGoPkg() string { - gopath := MustSetFirstGopath(MustGetGopath()) + gopath := MustSetWdGopath(MustGetGopath()) return parseGoPkg(gopath) } @@ -187,9 +187,9 @@ func GetGoPkg() string { } goPath = filepath.Join(hd, "go", "src") } else { - // MustSetFirstGopath is necessary here because the user has set GOPATH, + // MustSetWdGopath is necessary here because the user has set GOPATH, // which could be a path list. - goPath = MustSetFirstGopath(goPath) + goPath = MustSetWdGopath(goPath) } if !strings.HasPrefix(MustGetwd(), goPath) { log.Fatal("Could not determine project repository path: $GOPATH not set, wd in default $HOME/go/src, or wd does not contain a go.mod") @@ -245,10 +245,10 @@ func MustGetGopath() string { return gopath } -// MustSetFirstGopath sets GOPATH to the first element of the path list in -// currentGopath, then returns the set path. -// If GOPATH cannot be set, MustSetFirstGopath exits. -func MustSetFirstGopath(currentGopath string) string { +// MustSetWdGopath sets GOPATH to the first element of the path list in +// currentGopath that prefixes the wd, then returns the set path. +// If GOPATH cannot be set, MustSetWdGopath exits. +func MustSetWdGopath(currentGopath string) string { var ( newGopath string cwdInGopath bool From 7d7f0506d07e69c5a00825ac9b3efa5d20f054d1 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 11 Jun 2019 10:53:52 -0700 Subject: [PATCH 21/35] remove unused function --- cmd/operator-sdk/new/cmd.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index 11166ed651a..5abb2e64d51 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -469,29 +469,6 @@ func getDeps() error { return nil } -func checkProject() error { - log.Info("Checking project") - switch projutil.DepManagerType(depManager) { - case projutil.DepManagerGoMod: - // Run "go build ./..." to make sure all packages can be built - // currectly. From "go help build": - // - // When compiling multiple packages or a single non-main package, - // build compiles the packages but discards the resulting object, - // serving only as a check that the packages can be built. - opts := projutil.GoCmdOptions{ - PackagePath: "./...", - Dir: filepath.Join(projutil.MustGetwd(), projectName), - } - if err := projutil.GoBuild(opts); err != nil { - return err - } - } - - log.Info("Check project successful.") - return nil -} - func initGit() error { if skipGit { return nil From ead25b9d986e49b0c1ec2c41fce2f02e8d0912ac Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 13 Jun 2019 10:35:38 -0700 Subject: [PATCH 22/35] updates based on PR comments --- cmd/operator-sdk/build/cmd.go | 6 ++-- cmd/operator-sdk/main.go | 8 +++-- cmd/operator-sdk/migrate/cmd.go | 2 +- cmd/operator-sdk/new/cmd.go | 38 ++++++++++------------- hack/image/build-scorecard-proxy-image.sh | 12 ++++--- hack/tests/e2e-ansible-molecule.sh | 3 +- hack/tests/e2e-ansible.sh | 3 +- hack/tests/e2e-helm.sh | 1 - 8 files changed, 35 insertions(+), 38 deletions(-) diff --git a/cmd/operator-sdk/build/cmd.go b/cmd/operator-sdk/build/cmd.go index bfcd041f0dd..5eb64c82508 100644 --- a/cmd/operator-sdk/build/cmd.go +++ b/cmd/operator-sdk/build/cmd.go @@ -98,16 +98,14 @@ func buildFunc(cmd *cobra.Command, args []string) error { // Don't need to build Go code if a non-Go Operator. if projutil.IsOperatorGo() { + trimPath := fmt.Sprintf("all=-trimpath=%s", filepath.Dir(absProjectPath)) opts := projutil.GoCmdOptions{ BinName: filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName), PackagePath: path.Join(projutil.GetGoPkg(), filepath.ToSlash(scaffold.ManagerDir)), + Args: []string{"-gcflags", trimPath, "-asmflags", trimPath}, Env: goBuildEnv, GoMod: projutil.IsDepManagerGoMod(), } - if _, ok := os.LookupEnv(projutil.GoPathEnv); ok { - trimPath := os.ExpandEnv("all=-trimpath=${GOPATH}") - opts.Args = []string{"-gcflags", trimPath, "-asmflags", trimPath} - } if err := projutil.GoBuild(opts); err != nil { return fmt.Errorf("failed to build operator binary: (%v)", err) } diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index dce619e6b18..1dae60b86dc 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -93,11 +93,11 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { } var dm projutil.DepManagerType - switch cmd.Name() { + switch n := cmd.Name(); n { case "new", "migrate": // Do not perform this check if the new project is non-Go, as they do not // have (Go) dep managers. - if cmd.Name() == "new" { + if n == "new" { projType, err := cmd.Flags().GetString("type") if err != nil { return err @@ -115,6 +115,10 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { } dm = projutil.DepManagerType(dmStr) default: + // version and completion commands are able to be run anywhere. + if n == "version" || n == "completion" { + return nil + } // Do not perform this check if the project is non-Go, as they do not // have (Go) dep managers. if !projutil.IsOperatorGo() { diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 96ec06b4b79..8cbcca1ccbf 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -47,7 +47,7 @@ func NewCmd() *cobra.Command { newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") - newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's go import path. This must be set if outside of $GOPATH/src, and cannot be set of --dep-manager=dep") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's Go import path. This must be set if outside of $GOPATH/src, and cannot be set if --dep-manager=dep") return newCmd } diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index 5abb2e64d51..7fd3166dde4 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -58,7 +58,7 @@ generates a skeletal app-operator application in $HOME/projects/example.com/app- newCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService) - used with \"ansible\" or \"helm\" types") newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (choices: \"go\", \"ansible\" or \"helm\")") newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) - newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's go import path. This must be set if outside of $GOPATH/src, and cannot be set of --dep-manager=dep") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of $GOPATH/src, and cannot be set if --dep-manager=dep") newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository") newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") newCmd.Flags().BoolVar(&makeVendor, "vendor", false, "Use a vendor directory for dependencies. This flag only applies when --dep-manager=modules (the default)") @@ -99,14 +99,13 @@ func newFunc(cmd *cobra.Command, args []string) error { return err } - if repo == "" { - repo = path.Join(projutil.GetGoPkg(), projectName) - } - log.Infof("Creating new %s operator '%s'.", strings.Title(operatorType), projectName) switch operatorType { case projutil.OperatorTypeGo: + if repo == "" { + repo = path.Join(projutil.GetGoPkg(), projectName) + } if err := doGoScaffold(); err != nil { return err } @@ -221,7 +220,6 @@ func doGoScaffold() error { func doAnsibleScaffold() error { cfg := &input.Config{ - Repo: repo, AbsProjectPath: filepath.Join(projutil.MustGetwd(), projectName), ProjectName: projectName, } @@ -306,7 +304,6 @@ func doAnsibleScaffold() error { func doHelmScaffold() error { cfg := &input.Config{ - Repo: repo, AbsProjectPath: filepath.Join(projutil.MustGetwd(), projectName), ProjectName: projectName, } @@ -394,6 +391,19 @@ func verifyFlags() error { if !makeVendor && projutil.DepManagerType(depManager) == projutil.DepManagerDep { log.Warnf("--dep-manager=dep requires a vendor directory; ignoring --vendor=false") } + // dep assumes the project's path under $GOPATH/src is the project's + // repo path. + if repo != "" && depManager == string(projutil.DepManagerDep) { + return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) + } + + inGopathSrc, err := projutil.WdInGoPathSrc() + if err != nil { + return err + } + if !inGopathSrc && repo == "" && depManager == string(projutil.DepManagerGoMod) { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) + } } // --api-version and --kind are required with --type=ansible and --type=helm, with one exception. @@ -416,20 +426,6 @@ func verifyFlags() error { } } - // dep assumes the project's path under $GOPATH/src is the project's - // repo path. - if repo != "" && depManager == string(projutil.DepManagerDep) { - return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) - } - - inGopathSrc, err := projutil.WdInGoPathSrc() - if err != nil { - return err - } - if !inGopathSrc && repo == "" && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) - } - return nil } diff --git a/hack/image/build-scorecard-proxy-image.sh b/hack/image/build-scorecard-proxy-image.sh index b1dd1f808ef..860bfba0baa 100755 --- a/hack/image/build-scorecard-proxy-image.sh +++ b/hack/image/build-scorecard-proxy-image.sh @@ -3,11 +3,13 @@ set -eux # build operator binary and base image -GCFLAGS= -if [[ -n "$GOPATH" ]]; then - GCFLAGS="-gcflags \"all=-trimpath=${GOPATH}\" -asmflags \"all=-trimpath=${GOPATH}\"" -fi -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $GCFLAGS -o images/scorecard-proxy/scorecard-proxy images/scorecard-proxy/cmd/proxy/main.go +WD="$(dirname "$(pwd)")" +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ + go build \ + -gcflags "all=-trimpath=${WD}" \ + -asmflags "all=-trimpath=${WD}" \ + -o images/scorecard-proxy/scorecard-proxy \ + images/scorecard-proxy/cmd/proxy/main.go pushd images/scorecard-proxy docker build -t "$1" . popd diff --git a/hack/tests/e2e-ansible-molecule.sh b/hack/tests/e2e-ansible-molecule.sh index 6c46e6b21a1..6f600f6cf5e 100755 --- a/hack/tests/e2e-ansible-molecule.sh +++ b/hack/tests/e2e-ansible-molecule.sh @@ -31,8 +31,7 @@ operator-sdk new memcached-operator \ --api-version=ansible.example.com/v1alpha1 \ --kind=Memcached \ --type=ansible \ - --generate-playbook \ - --repo=github.com/example-inc/memcached-operator + --generate-playbook cp "$ROOTDIR/test/ansible-memcached/tasks.yml" memcached-operator/roles/memcached/tasks/main.yml cp "$ROOTDIR/test/ansible-memcached/defaults.yml" memcached-operator/roles/memcached/defaults/main.yml cp "$ROOTDIR/test/ansible-memcached/asserts.yml" memcached-operator/molecule/default/asserts.yml diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index bd8903434ba..fc899c1292e 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -100,8 +100,7 @@ pushd "$GOTMP" operator-sdk new memcached-operator \ --api-version=ansible.example.com/v1alpha1 \ --kind=Memcached \ - --type=ansible \ - --repo=github.com/example-inc/memcached-operator + --type=ansible cp "$ROOTDIR/test/ansible-memcached/tasks.yml" memcached-operator/roles/memcached/tasks/main.yml cp "$ROOTDIR/test/ansible-memcached/defaults.yml" memcached-operator/roles/memcached/defaults/main.yml cp -a "$ROOTDIR/test/ansible-memcached/memfin" memcached-operator/roles/ diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index c1d4de7e5b0..daf14bd7a3e 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -112,7 +112,6 @@ log=$(operator-sdk new nginx-operator \ --api-version=helm.example.com/v1alpha1 \ --kind=Nginx \ --type=helm \ - --repo=github.com/example-inc/nginx-operator \ 2>&1) echo $log if echo $log | grep -q "failed to generate RBAC rules"; then From 9b1ae6faacf1ef08c2d017956d010c4409aea5b7 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Thu, 13 Jun 2019 12:10:43 -0700 Subject: [PATCH 23/35] exit if go.mod exists but has incorrect permissions or a filesystem error occurs --- internal/util/projutil/project_util.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 2a71177b002..ca1a52e9a48 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -156,7 +156,9 @@ func getHomeDir() (string, error) { func GetGoPkg() string { // Default to reading from go.mod, as it should usually have the (correct) // package path, and no further processing need be done on it if so. - if _, err := os.Stat(goModFile); err == nil { + if _, err := os.Stat(goModFile); err != nil && !os.IsNotExist(err) { + log.Fatalf("Failed to read go.mod: %v", err) + } else if err == nil { b, err := ioutil.ReadFile(goModFile) if err != nil { log.Fatalf("Read go.mod: %v", err) From 5142a336036222b30c7922e0a464b910e42f7136 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 14 Jun 2019 11:34:14 -0700 Subject: [PATCH 24/35] Apply suggestions from code review Co-Authored-By: Haseeb Tariq Co-Authored-By: Joe Lanford --- cmd/operator-sdk/main.go | 4 ++-- cmd/operator-sdk/migrate/cmd.go | 2 +- cmd/operator-sdk/new/cmd.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index 1dae60b86dc..18dce230c96 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -136,7 +136,7 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { return err } if !goModOn { - return fmt.Errorf(`depedency manger "modules" requires wd be in $GOPATH/src` + + return fmt.Errorf(`dependency manager "modules" requires working directory to be in $GOPATH/src` + ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="on", "auto", or unset`) } case projutil.DepManagerDep: @@ -145,7 +145,7 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { return err } if !inGopathSrc { - return fmt.Errorf(`depedency manger "dep" requires wd be in $GOPATH/src`) + return fmt.Errorf(`dependency manager "dep" requires working directory to be in $GOPATH/src`) } } diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 8cbcca1ccbf..507692d7565 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -47,7 +47,7 @@ func NewCmd() *cobra.Command { newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") - newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's Go import path. This must be set if outside of $GOPATH/src, and cannot be set if --dep-manager=dep") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's Go import path. This must be set if outside of $GOPATH/src with go modules, and cannot be set if --dep-manager=dep") return newCmd } diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index 7fd3166dde4..5becd178259 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -58,7 +58,7 @@ generates a skeletal app-operator application in $HOME/projects/example.com/app- newCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService) - used with \"ansible\" or \"helm\" types") newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (choices: \"go\", \"ansible\" or \"helm\")") newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) - newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of $GOPATH/src, and cannot be set if --dep-manager=dep") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of $GOPATH/src with go modules, and cannot be set if --dep-manager=dep") newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository") newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") newCmd.Flags().BoolVar(&makeVendor, "vendor", false, "Use a vendor directory for dependencies. This flag only applies when --dep-manager=modules (the default)") From 29cfc803ccbb1c5f2e0d709aa5152d1273a2c585 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 14 Jun 2019 11:39:29 -0700 Subject: [PATCH 25/35] updates from PR comments --- cmd/operator-sdk/main.go | 81 +++++++++++++++++---------------- cmd/operator-sdk/migrate/cmd.go | 24 ++++++---- cmd/operator-sdk/new/cmd.go | 34 ++++++++------ 3 files changed, 80 insertions(+), 59 deletions(-) diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index 18dce230c96..4854a3491c9 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -86,49 +86,55 @@ func main() { } func checkDepManagerForCmd(cmd *cobra.Command) (err error) { - // Do not perform this check if wd is not in the project root, - // as some sub-commands might not require project root. + // Certain commands are able to be run anywhere or handle this check + // differently in their CLI code. + if skipCheckForCmd(cmd) { + return nil + } + // Do not perform this check if the project is non-Go, as thy will not have + // a (Go) dep manager. + if !projutil.IsOperatorGo() { + return nil + } + // Do not perform a dep manager check if the working directory is not in + // the project root, as some sub-commands might not require project root. + // Individual subcommands will perform this check as needed. if err := projutil.CheckProjectRoot(); err != nil { return nil } - var dm projutil.DepManagerType - switch n := cmd.Name(); n { - case "new", "migrate": - // Do not perform this check if the new project is non-Go, as they do not - // have (Go) dep managers. - if n == "new" { - projType, err := cmd.Flags().GetString("type") - if err != nil { - return err - } - if projType != "go" { - return nil + dm, err := projutil.GetDepManagerType() + if err != nil { + return err + } + return checkDepManager(dm) +} + +var commandsToSkip = map[string]struct{}{ + "new": struct{}{}, // Handles this logic in cmd/operator-sdk/new + "migrate": struct{}{}, // Handles this logic in cmd/operator-sdk/migrate + "operator-sdk": struct{}{}, // Alias for "help" + "help": struct{}{}, + "completion": struct{}{}, + "version": struct{}{}, +} + +func skipCheckForCmd(cmd *cobra.Command) (skip bool) { + if _, ok := commandsToSkip[cmd.Name()]; ok { + return true + } + cmd.VisitParents(func(pc *cobra.Command) { + if _, ok := commandsToSkip[pc.Name()]; ok { + // The bare "operator-sdk" command will be checked above. + if pc.Name() != "operator-sdk" { + skip = true } } - // "new" and "migrate" commands are for projects that establish which - // dep manager to use. "new" should not be called if we're in the project - // root but could be, so check it anyway. - dmStr, err := cmd.Flags().GetString("dep-manager") - if err != nil { - return err - } - dm = projutil.DepManagerType(dmStr) - default: - // version and completion commands are able to be run anywhere. - if n == "version" || n == "completion" { - return nil - } - // Do not perform this check if the project is non-Go, as they do not - // have (Go) dep managers. - if !projutil.IsOperatorGo() { - return nil - } - if dm, err = projutil.GetDepManagerType(); err != nil { - return err - } - } + }) + return skip +} +func checkDepManager(dm projutil.DepManagerType) error { switch dm { case projutil.DepManagerGoMod: goModOn, err := projutil.GoModOn() @@ -148,6 +154,5 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { return fmt.Errorf(`dependency manager "dep" requires working directory to be in $GOPATH/src`) } } - - return nil + return projutil.ErrInvalidDepManager(dm) } diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 507692d7565..d58272a17f0 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -76,18 +76,26 @@ func migrateRun(cmd *cobra.Command, args []string) error { } func verifyFlags() error { - // dep assumes the project's path under $GOPATH/src is the project's - // repo path. - if repo != "" && depManager == string(projutil.DepManagerDep) { - return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) - } - inGopathSrc, err := projutil.WdInGoPathSrc() if err != nil { return err } - if !inGopathSrc && repo == "" && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) + switch projutil.DepManagerType(depManager) { + case projutil.DepManagerDep: + // dep assumes the project's path under $GOPATH/src is the project's + // repo path. + if repo != "" { + return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) + } + if !inGopathSrc { + return fmt.Errorf(`depedency manger "dep" requires wd be in $GOPATH/src`) + } + case projutil.DepManagerGoMod: + if !inGopathSrc && repo == "" { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if the working directory is not in $GOPATH/src`) + } + default: + return projutil.ErrInvalidDepManager(depManager) } return nil diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index 5becd178259..3f794737bf9 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -187,8 +187,8 @@ func doGoScaffold() error { if goModOn, merr := projutil.GoModOn(); merr != nil { return merr } else if !goModOn { - log.Fatalf(`Dependency manager "%s" has been selected but go modules are not active. `+ - `Activate modules then run "operator-sdk new %s".`, m, projectName) + return errors.New(`dependency manager "modules" requires working directory to be in $GOPATH/src` + + ` and GO111MODULE=on, or outside of $GOPATH/src and GO111MODULE="on", "auto", or unset`) } err = s.Execute(cfg, &scaffold.GoMod{}, &scaffold.Tools{}) default: @@ -388,21 +388,29 @@ func verifyFlags() error { if len(apiVersion) != 0 || len(kind) != 0 { return fmt.Errorf("operators of type Go do not use --api-version or --kind") } - if !makeVendor && projutil.DepManagerType(depManager) == projutil.DepManagerDep { - log.Warnf("--dep-manager=dep requires a vendor directory; ignoring --vendor=false") - } - // dep assumes the project's path under $GOPATH/src is the project's - // repo path. - if repo != "" && depManager == string(projutil.DepManagerDep) { - return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) - } - inGopathSrc, err := projutil.WdInGoPathSrc() if err != nil { return err } - if !inGopathSrc && repo == "" && depManager == string(projutil.DepManagerGoMod) { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if wd not in $GOPATH/src`) + switch projutil.DepManagerType(depManager) { + case projutil.DepManagerDep: + if !makeVendor { + log.Warnf("--dep-manager=dep requires a vendor directory; ignoring --vendor=false") + } + // dep assumes the project's path under $GOPATH/src is the project's + // repo path. + if repo != "" { + return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) + } + if !inGopathSrc { + return fmt.Errorf(`depedency manger "dep" requires the working directory be in $GOPATH/src`) + } + case projutil.DepManagerGoMod: + if !inGopathSrc && repo == "" { + return fmt.Errorf(`depedency manger "modules" requires --repo be set if the working directory is not in $GOPATH/src`) + } + default: + return projutil.ErrInvalidDepManager(depManager) } } From e848c48e8d987d1cef6010e397df759f17033541 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 14 Jun 2019 12:31:01 -0700 Subject: [PATCH 26/35] some updates --- cmd/operator-sdk/main.go | 4 +++- internal/util/projutil/project_util.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index 4854a3491c9..1ed7f098525 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -153,6 +153,8 @@ func checkDepManager(dm projutil.DepManagerType) error { if !inGopathSrc { return fmt.Errorf(`dependency manager "dep" requires working directory to be in $GOPATH/src`) } + default: + return projutil.ErrInvalidDepManager(dm) } - return projutil.ErrInvalidDepManager(dm) + return nil } diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index ca1a52e9a48..11107380886 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -79,7 +79,7 @@ const ( type ErrInvalidDepManager string func (e ErrInvalidDepManager) Error() string { - return fmt.Sprintf(`"%s" is not a valid dep manager; dep manager must be one of ["%v", "%v"]`, e, DepManagerDep, DepManagerGoMod) + return fmt.Sprintf(`"%s" is not a valid dep manager; dep manager must be one of ["%v", "%v"]`, string(e), DepManagerDep, DepManagerGoMod) } var ErrNoDepManager = fmt.Errorf(`no valid dependency manager file found; dep manager must be one of ["%v", "%v"]`, DepManagerDep, DepManagerGoMod) From e02e08eace03476067f9242a91cb6396a625f4d7 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 14 Jun 2019 15:34:00 -0700 Subject: [PATCH 27/35] Update cmd/operator-sdk/main.go Co-Authored-By: Haseeb Tariq --- cmd/operator-sdk/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index 1ed7f098525..7baf705deef 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -91,7 +91,7 @@ func checkDepManagerForCmd(cmd *cobra.Command) (err error) { if skipCheckForCmd(cmd) { return nil } - // Do not perform this check if the project is non-Go, as thy will not have + // Do not perform this check if the project is non-Go, as they will not have // a (Go) dep manager. if !projutil.IsOperatorGo() { return nil From 4b2926b4612e84e1d6f42d048eb96f9eef104150 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Mon, 17 Jun 2019 10:38:44 -0700 Subject: [PATCH 28/35] Apply suggestions from code review Co-Authored-By: Haseeb Tariq --- cmd/operator-sdk/migrate/cmd.go | 4 ++-- cmd/operator-sdk/new/cmd.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index d58272a17f0..441d929a2b6 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -88,11 +88,11 @@ func verifyFlags() error { return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) } if !inGopathSrc { - return fmt.Errorf(`depedency manger "dep" requires wd be in $GOPATH/src`) + return fmt.Errorf(`dependency manager "dep" requires working directory be in $GOPATH/src`) } case projutil.DepManagerGoMod: if !inGopathSrc && repo == "" { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if the working directory is not in $GOPATH/src`) + return fmt.Errorf(`dependency manager "modules" requires flag --repo to be set if the working directory is not in $GOPATH/src`) } default: return projutil.ErrInvalidDepManager(depManager) diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index e13b1672bdc..e2c18df847f 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -403,11 +403,11 @@ func verifyFlags() error { return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) } if !inGopathSrc { - return fmt.Errorf(`depedency manger "dep" requires the working directory be in $GOPATH/src`) + return fmt.Errorf(`dependency manager "dep" requires the working directory to be in $GOPATH/src`) } case projutil.DepManagerGoMod: if !inGopathSrc && repo == "" { - return fmt.Errorf(`depedency manger "modules" requires --repo be set if the working directory is not in $GOPATH/src`) + return fmt.Errorf(`dependency manager "modules" requires flag --repo be set if the working directory is not in $GOPATH/src`) } default: return projutil.ErrInvalidDepManager(depManager) From 65fea0fd9df568f07f27fb6816caf2aeb12b6792 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 18 Jun 2019 09:03:27 -0700 Subject: [PATCH 29/35] refactor dep/repo check into a projutil function --- cmd/operator-sdk/migrate/cmd.go | 20 +----------------- cmd/operator-sdk/new/cmd.go | 27 ++++++------------------ internal/util/projutil/project_util.go | 29 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 441d929a2b6..914c321ee46 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -76,28 +76,10 @@ func migrateRun(cmd *cobra.Command, args []string) error { } func verifyFlags() error { - inGopathSrc, err := projutil.WdInGoPathSrc() + err := projutil.CheckDepManagerWithRepo(projutil.DepManagerType(depManager), repo) if err != nil { return err } - switch projutil.DepManagerType(depManager) { - case projutil.DepManagerDep: - // dep assumes the project's path under $GOPATH/src is the project's - // repo path. - if repo != "" { - return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) - } - if !inGopathSrc { - return fmt.Errorf(`dependency manager "dep" requires working directory be in $GOPATH/src`) - } - case projutil.DepManagerGoMod: - if !inGopathSrc && repo == "" { - return fmt.Errorf(`dependency manager "modules" requires flag --repo to be set if the working directory is not in $GOPATH/src`) - } - default: - return projutil.ErrInvalidDepManager(depManager) - } - return nil } diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index e2c18df847f..a57b7d4aad8 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -388,30 +388,15 @@ func verifyFlags() error { if len(apiVersion) != 0 || len(kind) != 0 { return fmt.Errorf("operators of type Go do not use --api-version or --kind") } - inGopathSrc, err := projutil.WdInGoPathSrc() + + dm := projutil.DepManagerType(depManager) + if !makeVendor && dm == projutil.DepManagerDep { + log.Warnf("--dep-manager=dep requires a vendor directory; ignoring --vendor=false") + } + err := projutil.CheckDepManagerWithRepo(dm, repo) if err != nil { return err } - switch projutil.DepManagerType(depManager) { - case projutil.DepManagerDep: - if !makeVendor { - log.Warnf("--dep-manager=dep requires a vendor directory; ignoring --vendor=false") - } - // dep assumes the project's path under $GOPATH/src is the project's - // repo path. - if repo != "" { - return fmt.Errorf(`--repo flag cannot be used with --dep-manger=dep`) - } - if !inGopathSrc { - return fmt.Errorf(`dependency manager "dep" requires the working directory to be in $GOPATH/src`) - } - case projutil.DepManagerGoMod: - if !inGopathSrc && repo == "" { - return fmt.Errorf(`dependency manager "modules" requires flag --repo be set if the working directory is not in $GOPATH/src`) - } - default: - return projutil.ErrInvalidDepManager(depManager) - } } // --api-version and --kind are required with --type=ansible and --type=helm, with one exception. diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 11107380886..eafe7a4b986 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -277,3 +277,32 @@ func SetGoVerbose() error { } return nil } + +// CheckDepManagerWithRepo ensures dm and repo are being used in combination +// correctly, as different dependency managers have different Go environment +// requirements. +func CheckDepManagerWithRepo(dm DepManagerType, repo string) error { + inGopathSrc, err := WdInGoPathSrc() + if err != nil { + return err + } + switch dm { + case DepManagerDep: + // dep assumes the project's path under $GOPATH/src is the project's + // repo path. + if repo != "" { + return fmt.Errorf(`repo cannot be set with dependency manager "dep"`) + } + if !inGopathSrc { + return fmt.Errorf(`dependency manager "dep" requires working directory to be in $GOPATH/src`) + } + case DepManagerGoMod: + if !inGopathSrc && repo == "" { + return fmt.Errorf(`dependency manager "modules" requires repo to be set if the working directory is not in $GOPATH/src`) + } + default: + return ErrInvalidDepManager(dm) + } + + return nil +} From d5408106666e06d5d287612d506637bf5c0087d1 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 18 Jun 2019 14:51:07 -0700 Subject: [PATCH 30/35] Apply suggestions from code review Co-Authored-By: Haseeb Tariq --- internal/util/projutil/project_util.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index eafe7a4b986..f0f6e763f9a 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -291,14 +291,14 @@ func CheckDepManagerWithRepo(dm DepManagerType, repo string) error { // dep assumes the project's path under $GOPATH/src is the project's // repo path. if repo != "" { - return fmt.Errorf(`repo cannot be set with dependency manager "dep"`) + return fmt.Errorf(`The flag --repo cannot be set with dependency manager "dep"`) } if !inGopathSrc { return fmt.Errorf(`dependency manager "dep" requires working directory to be in $GOPATH/src`) } case DepManagerGoMod: if !inGopathSrc && repo == "" { - return fmt.Errorf(`dependency manager "modules" requires repo to be set if the working directory is not in $GOPATH/src`) + return fmt.Errorf(`dependency manager "modules" requires the flag --repo to be set if the working directory is not in $GOPATH/src. See "operator-sdk new -h"`) } default: return ErrInvalidDepManager(dm) From a4d1c96b3162a6e0b455db5819f8aa0dcf1c49fc Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Jun 2019 09:03:54 -0700 Subject: [PATCH 31/35] Apply suggestions from code review Co-Authored-By: Lili Cosic --- internal/util/projutil/project_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index f0f6e763f9a..d98a1580473 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -278,7 +278,7 @@ func SetGoVerbose() error { return nil } -// CheckDepManagerWithRepo ensures dm and repo are being used in combination +// CheckDepManagerWithRepo ensures dependency manager type and repo are being used in combination // correctly, as different dependency managers have different Go environment // requirements. func CheckDepManagerWithRepo(dm DepManagerType, repo string) error { From d62b15c2bef6a092da6cddafbb01bc2b616e5a21 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Jun 2019 09:06:39 -0700 Subject: [PATCH 32/35] more informative log message --- internal/util/projutil/project_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index d98a1580473..b81fc9e29b9 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -291,7 +291,7 @@ func CheckDepManagerWithRepo(dm DepManagerType, repo string) error { // dep assumes the project's path under $GOPATH/src is the project's // repo path. if repo != "" { - return fmt.Errorf(`The flag --repo cannot be set with dependency manager "dep"`) + return fmt.Errorf(`The flag --repo cannot be set with dependency manager "dep", as dep always infers the repo path`) } if !inGopathSrc { return fmt.Errorf(`dependency manager "dep" requires working directory to be in $GOPATH/src`) From a90c037d6dbcd9de5c2ab8f3cba511eff687cd95 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Jun 2019 09:50:32 -0700 Subject: [PATCH 33/35] update "go modules" -> "Go modules" --- cmd/operator-sdk/migrate/cmd.go | 2 +- cmd/operator-sdk/new/cmd.go | 2 +- doc/user-guide.md | 4 ++-- hack/tests/e2e-ansible.sh | 2 +- hack/tests/e2e-helm.sh | 2 +- internal/util/projutil/exec.go | 4 ++-- internal/util/projutil/project_util.go | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/operator-sdk/migrate/cmd.go b/cmd/operator-sdk/migrate/cmd.go index 914c321ee46..a9387128610 100644 --- a/cmd/operator-sdk/migrate/cmd.go +++ b/cmd/operator-sdk/migrate/cmd.go @@ -47,7 +47,7 @@ func NewCmd() *cobra.Command { newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") - newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's Go import path. This must be set if outside of $GOPATH/src with go modules, and cannot be set if --dep-manager=dep") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path. Used as the project's Go import path. This must be set if outside of $GOPATH/src with Go modules, and cannot be set if --dep-manager=dep") return newCmd } diff --git a/cmd/operator-sdk/new/cmd.go b/cmd/operator-sdk/new/cmd.go index a57b7d4aad8..890c5d9b56f 100644 --- a/cmd/operator-sdk/new/cmd.go +++ b/cmd/operator-sdk/new/cmd.go @@ -58,7 +58,7 @@ generates a skeletal app-operator application in $HOME/projects/example.com/app- newCmd.Flags().StringVar(&kind, "kind", "", "Kubernetes CustomResourceDefintion kind. (e.g AppService) - used with \"ansible\" or \"helm\" types") newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (choices: \"go\", \"ansible\" or \"helm\")") newCmd.Flags().StringVar(&depManager, "dep-manager", "modules", `Dependency manager the new project will use (choices: "dep", "modules")`) - newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of $GOPATH/src with go modules, and cannot be set if --dep-manager=dep") + newCmd.Flags().StringVar(&repo, "repo", "", "Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of $GOPATH/src with Go modules, and cannot be set if --dep-manager=dep") newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository") newCmd.Flags().StringVar(&headerFile, "header-file", "", "Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt") newCmd.Flags().BoolVar(&makeVendor, "vendor", false, "Use a vendor directory for dependencies. This flag only applies when --dep-manager=modules (the default)") diff --git a/doc/user-guide.md b/doc/user-guide.md index d12bdc04cd6..0a5d9fa98a5 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -37,7 +37,7 @@ By default, `operator-sdk new` generates a `go.mod` file to be used with [Go mod ##### Go modules -If using go modules (the default dependency manager) in your project, ensure you activate module support before using the SDK. From the [go modules Wiki][go_mod_wiki]: +If using Go modules (the default dependency manager) in your project, ensure you activate module support before using the SDK. From the [Go modules Wiki][go_mod_wiki]: > You can activate module support in one of two ways: > - Invoke the go command in a directory outside of the $GOPATH/src tree, with a valid go.mod file in the current directory or any parent of it and the environment variable GO111MODULE unset (or explicitly set to auto). @@ -202,7 +202,7 @@ Once this is done, there are two ways to run the operator: **Note**: `operator-sdk build` invokes `docker build` by default, and optionally `buildah bud`. If using `buildah`, skip to the `operator-sdk build` invocation instructions below. If using `docker`, make sure your docker daemon is running and that you can run the docker client without sudo. You can check if this is the case by running `docker version`, which should complete without errors. Follow instructions for your OS/distribution on how to start the docker daemon and configure your access permissions, if needed. -**Note**: If using go modules and a `vendor/` directory, run +**Note**: If using Go modules and a `vendor/` directory, run ```sh $ go mod vendor ``` diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index fc899c1292e..a34bf32ba30 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -152,7 +152,7 @@ go build ./... # Use the local operator-sdk directory as the repo. To make the go toolchain # happy, the directory needs a `go.mod` file that specifies the module name, # so we need this temporary hack until we update the SDK repo itself to use -# go modules. +# Go modules. echo "module ${SDK_REPO}" > "${ROOTDIR}/go.mod" trap_add "rm ${ROOTDIR}/go.mod" EXIT go mod edit -replace="${SDK_REPO}=$ROOTDIR" diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index daf14bd7a3e..9b4dacaf468 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -161,7 +161,7 @@ go build ./... # Use the local operator-sdk directory as the repo. To make the go toolchain # happy, the directory needs a `go.mod` file that specifies the module name, # so we need this temporary hack until we update the SDK repo itself to use -# go modules. +# Go modules. echo "module ${SDK_REPO}" > "${ROOTDIR}/go.mod" trap_add "rm ${ROOTDIR}/go.mod" EXIT go mod edit -replace="${SDK_REPO}=$ROOTDIR" diff --git a/internal/util/projutil/exec.go b/internal/util/projutil/exec.go index fc9b27f250f..bb386aa0c9e 100644 --- a/internal/util/projutil/exec.go +++ b/internal/util/projutil/exec.go @@ -51,7 +51,7 @@ type GoCmdOptions struct { Dir string // GoMod determines whether to set the "-mod=vendor" flag. // If true and ./vendor/ exists, "go {cmd}" will use vendored modules. - // If false, "go {cmd}" will not use go modules. This is the default. + // If false, "go {cmd}" will not use Go modules. This is the default. // This applies to build, clean, get, install, list, run, and test. GoMod bool } @@ -157,7 +157,7 @@ func (opts GoCmdOptions) setCmdFields(c *exec.Cmd) { // the environment variable GO111MODULE unset (or explicitly set to auto). // - Invoke the go command with GO111MODULE=on environment variable set. // -// GoModOn returns true if go modules are on in one of the above two ways. +// GoModOn returns true if Go modules are on in one of the above two ways. func GoModOn() (bool, error) { v, ok := os.LookupEnv(GoModEnv) if v == "off" { diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index b81fc9e29b9..6a5d523b46d 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -150,7 +150,7 @@ func getHomeDir() (string, error) { // GetGoPkg returns the current directory's import path by parsing it from // wd if this project's repository path is rooted under $GOPATH/src, or -// from go.mod the project uses go modules to manage dependencies. +// from go.mod the project uses Go modules to manage dependencies. // // Example: "github.com/example-inc/app-operator" func GetGoPkg() string { From a1b4420f93f23bb64f6a80f51728d4f087c649dd Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Jun 2019 09:51:10 -0700 Subject: [PATCH 34/35] CHANGELOG.md: add GOPATH change and --repo addition --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2067e266e92..5f8f65afeb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,13 @@ - New flags `--vendor` and `--skip-validation` for [`operator-sdk new`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#new) that direct the SDK to initialize a new project with a `vendor/` directory, and without validating project dependencies. `vendor/` is not written by default. ([#1519](https://github.com/operator-framework/operator-sdk/pull/1519)) - Generating and serving info metrics about each custom resource. By default these metrics are exposed on port 8686. ([#1277](https://github.com/operator-framework/operator-sdk/pull/1277)) - Scaffold a `pkg/apis//group.go` package file to avoid `go/build` errors when running Kubernetes code generators. ([#1401](https://github.com/operator-framework/operator-sdk/pull/1401)) +- New flag `--repo` for subcommands [`new`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#new) and [`migrate`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#migrate) specifies the repository path to be used in Go source files generated by the SDK. This flag can only be used with [Go modules](https://github.com/golang/go/wiki/Modules). ([#1475](https://github.com/operator-framework/operator-sdk/pull/1475)) ### Changed - Remove TypeMeta declaration from the implementation of the objects [#1462](https://github.com/operator-framework/operator-sdk/pull/1462/) - Relaxed API version format check when parsing `pkg/apis` in code generators. API dir structures can now be of the format `pkg/apis//`, where `` was previously required to be in the Kubernetes version format, ex. `v1alpha1`. ([#1525](https://github.com/operator-framework/operator-sdk/pull/1525)) +- The SDK and operator projects will work outside of `$GOPATH/src` when using [Go modules](https://github.com/golang/go/wiki/Modules). ([#1475](https://github.com/operator-framework/operator-sdk/pull/1475)) ### Deprecated From a731517ede930e73d4f8985687a9a3440178c747 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 19 Jun 2019 09:52:11 -0700 Subject: [PATCH 35/35] doc/sdk-cli-reference.md: add --repo to new and migrate, and add/remove some flags where appropriate --- doc/sdk-cli-reference.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/sdk-cli-reference.md b/doc/sdk-cli-reference.md index 4e38824d480..f6ad43b0a05 100644 --- a/doc/sdk-cli-reference.md +++ b/doc/sdk-cli-reference.md @@ -136,10 +136,6 @@ Currently only runs `deepcopy-gen` to generate the required `DeepCopy()` functio **Note**: This command must be run every time the api (spec and status) for a custom resource type is updated. -### Flags - -* `--header-file` string - Path to file containing headers for generated files (optional). - #### Example ```console @@ -228,6 +224,8 @@ you will need to rename it before running migrate or manually add it to your Doc #### Flags * `--dep-manager` string - Dependency manager the migrated project will use (choices: "dep", "modules") (default "modules") +* `--header-file` string - Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt +* `--repo` string - Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of `$GOPATH/src` with Go modules, and cannot be set if `--dep-manager=dep` ### Example @@ -261,7 +259,9 @@ Scaffolds a new operator project. * `--helm-chart` string - Initialize helm operator with existing helm chart (``, `/`, or local path) * `--helm-chart-repo` string - Chart repository URL for the requested helm chart * `--helm-chart-version` string - Specific version of the helm chart (default is latest version) +* `--header-file` string - Path to file containing headers for generated Go files. Copied to hack/boilerplate.go.txt * `--dep-manager` string - Dependency manager the new project will use (choices: "dep", "modules") (default "modules") +* `--repo` string - Project repository path for Go operators. Used as the project's Go import path. This must be set if outside of `$GOPATH/src` with Go modules, and cannot be set if `--dep-manager=dep` * `--skip-git-init` - Do not init the directory as a git repository * `--vendor` - Use a vendor directory for dependencies. This flag only applies when `--dep-manager=modules` (the default) * `--skip-validation` - Do not validate the resulting project's structure and dependencies. (Only used for --type go) @@ -330,7 +330,6 @@ Adds the API definition for a new custom resource under `pkg/apis` and generates * `--api-version` string - CRD APIVersion in the format `$GROUP_NAME/$VERSION` (e.g app.example.com/v1alpha1) * `--kind` string - CRD Kind. (e.g AppService) -* `--header-file` string - Path to file containing headers for generated files (optional). #### Example