From 36f2d1f2f3e4343d60b8d78756aebd7858c4b0d0 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 24 Jul 2020 15:10:03 -0700 Subject: [PATCH] cmd/operator-sdk: remove legacy CLI from public exposure entirely internal: clean up utils --- cmd/operator-sdk/build/cmd.go | 48 +-- cmd/operator-sdk/cli/cli.go | 2 +- cmd/operator-sdk/cli/legacy.go | 113 ------ cmd/operator-sdk/generate/bundle/cmd.go | 4 +- .../generate/kustomize/manifests.go | 3 +- .../generate/packagemanifests/cmd.go | 4 +- cmd/operator-sdk/main.go | 37 +- go.mod | 2 - go.sum | 3 +- internal/annotations/prefix.go | 112 ------ internal/annotations/prefix_test.go | 96 ----- .../clusterserviceversion_test.go | 3 +- internal/scaffold/constants.go | 39 --- internal/scaffold/cr.go | 71 ---- internal/scaffold/cr_test.go | 76 ---- internal/scaffold/customrender.go | 29 -- internal/scaffold/input/input.go | 131 ------- .../scaffold/internal/deps/print_go_mod.go | 37 -- .../scaffold/internal/testutil/test_util.go | 43 --- internal/scaffold/resource.go | 152 -------- internal/scaffold/role.go | 329 ------------------ internal/scaffold/role_test.go | 192 ---------- internal/scaffold/rolebinding.go | 54 --- internal/scaffold/rolebinding_test.go | 75 ---- internal/scaffold/scaffold.go | 207 ----------- internal/scaffold/service_account.go | 41 --- internal/scaffold/service_account_test.go | 40 --- internal/scaffold/test_setup.go | 59 ---- internal/util/fileutil/file_util.go | 119 ------- internal/util/k8sutil/api.go | 80 ----- internal/util/k8sutil/constants.go | 8 - internal/util/k8sutil/manifest.go | 143 -------- internal/util/kubebuilder/constants.go | 19 - internal/util/kubebuilder/project.go | 52 --- internal/util/projutil/exec.go | 139 -------- internal/util/projutil/project_util.go | 255 ++------------ pkg/ansible/proxy/kubeconfig/kubeconfig.go | 5 +- pkg/ansible/proxy/proxy_test.go | 5 +- pkg/ansible/runner/eventapi/eventapi.go | 6 +- .../runner/internal/inputdir/inputdir.go | 5 +- pkg/ansible/watches/watches.go | 26 +- pkg/ansible/watches/watches_test.go | 23 +- pkg/helm/controller/reconcile.go | 8 +- .../helm/internal/diff/diff.go | 5 +- 44 files changed, 91 insertions(+), 2809 deletions(-) delete mode 100644 cmd/operator-sdk/cli/legacy.go delete mode 100644 internal/annotations/prefix.go delete mode 100644 internal/annotations/prefix_test.go delete mode 100644 internal/scaffold/constants.go delete mode 100644 internal/scaffold/cr.go delete mode 100644 internal/scaffold/cr_test.go delete mode 100644 internal/scaffold/customrender.go delete mode 100644 internal/scaffold/input/input.go delete mode 100644 internal/scaffold/internal/deps/print_go_mod.go delete mode 100644 internal/scaffold/internal/testutil/test_util.go delete mode 100644 internal/scaffold/resource.go delete mode 100644 internal/scaffold/role.go delete mode 100644 internal/scaffold/role_test.go delete mode 100644 internal/scaffold/rolebinding.go delete mode 100644 internal/scaffold/rolebinding_test.go delete mode 100644 internal/scaffold/scaffold.go delete mode 100644 internal/scaffold/service_account.go delete mode 100644 internal/scaffold/service_account_test.go delete mode 100644 internal/scaffold/test_setup.go delete mode 100644 internal/util/fileutil/file_util.go delete mode 100644 internal/util/k8sutil/manifest.go delete mode 100644 internal/util/kubebuilder/constants.go delete mode 100644 internal/util/kubebuilder/project.go rename internal/util/diffutil/diff_util.go => pkg/helm/internal/diff/diff.go (94%) diff --git a/cmd/operator-sdk/build/cmd.go b/cmd/operator-sdk/build/cmd.go index a885a091de..8bb3dbb216 100644 --- a/cmd/operator-sdk/build/cmd.go +++ b/cmd/operator-sdk/build/cmd.go @@ -16,14 +16,9 @@ package build import ( "fmt" - "os" "os/exec" - "path" "path/filepath" - "strings" - "github.com/operator-framework/operator-sdk/internal/scaffold" - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/google/shlex" @@ -65,7 +60,7 @@ For example: "Tool to build OCI images. One of: [docker, podman, buildah]") // todo: remove when the legacy layout is no longer supported - if !kbutil.HasProjectFile() { + if !projutil.HasProjectFile() { buildCmd.Flags().StringVar(&goBuildArgs, "go-build-args", "", "Extra Go build arguments as one string such as \"-ldflags -X=main.xyz=abc\"") } @@ -104,9 +99,8 @@ func buildFunc(cmd *cobra.Command, args []string) error { } image := args[0] - projutil.MustInProjectRoot() - if kbutil.HasProjectFile() { + if projutil.HasProjectFile() { if err := doImageBuild("Dockerfile", image); err != nil { log.Fatalf("Failed to build image %s: %v", image, err) } @@ -115,48 +109,12 @@ func buildFunc(cmd *cobra.Command, args []string) error { // todo: remove when the legacy layout is no longer supported // note that the above if will no longer be required as well. - if err := doLegacyBuild(image); err != nil { + if err := doImageBuild(filepath.Join("build", "Dockerfile"), image); err != nil { log.Fatalf("Failed to build image %s: %v", image, err) } return nil } -// todo: remove when the legacy layout is no longer supported -// Deprecated: Used just for the legacy layout -// -- -// doLegacyBuild will build projects with the legacy layout. -func doLegacyBuild(image string) error { - goBuildEnv := append(os.Environ(), "GOOS=linux") - // If CGO_ENABLED is not set, set it to '0'. - if _, ok := os.LookupEnv("CGO_ENABLED"); !ok { - goBuildEnv = append(goBuildEnv, "CGO_ENABLED=0") - } - absProjectPath := projutil.MustGetwd() - projectName := filepath.Base(absProjectPath) - - // Don't need to build Go code if a non-Go Operator. - if projutil.IsOperatorGo() { - trimPath := fmt.Sprintf("all=-trimpath=%s", filepath.Dir(absProjectPath)) - args := []string{"-gcflags", trimPath, "-asmflags", trimPath} - - if goBuildArgs != "" { - splitArgs := strings.Fields(goBuildArgs) - args = append(args, splitArgs...) - } - - opts := projutil.GoCmdOptions{ - BinName: filepath.Join(absProjectPath, scaffold.BuildBinDir, projectName), - PackagePath: path.Join(projutil.GetGoPkg(), filepath.ToSlash(scaffold.ManagerDir)), - Args: args, - Env: goBuildEnv, - } - if err := projutil.GoBuild(opts); err != nil { - log.Fatalf("Failed to build operator binary: %v", err) - } - } - return doImageBuild("build/Dockerfile", image) -} - // doImageBuild will execute the build command for the given Dockerfile path and image func doImageBuild(dockerFilePath, image string) error { log.Infof("Building OCI image %s", image) diff --git a/cmd/operator-sdk/cli/cli.go b/cmd/operator-sdk/cli/cli.go index ddd0ae36d6..24d5fd31e6 100644 --- a/cmd/operator-sdk/cli/cli.go +++ b/cmd/operator-sdk/cli/cli.go @@ -37,7 +37,6 @@ import ( ) var commands = []*cobra.Command{ - scorecard.NewCmd(), build.NewCmd(), bundle.NewCmd(), cleanup.NewCmd(), @@ -45,6 +44,7 @@ var commands = []*cobra.Command{ generate.NewCmd(), olm.NewCmd(), run.NewCmd(), + scorecard.NewCmd(), version.NewCmd(), } diff --git a/cmd/operator-sdk/cli/legacy.go b/cmd/operator-sdk/cli/legacy.go deleted file mode 100644 index 329f3ef943..0000000000 --- a/cmd/operator-sdk/cli/legacy.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cli - -import ( - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/build" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/completion" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/olm" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/scorecard" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/version" - "github.com/operator-framework/operator-sdk/internal/flags" - "github.com/operator-framework/operator-sdk/internal/util/projutil" -) - -func RunLegacy() error { - root := GetCLIRoot() - root.PersistentFlags().Bool(flags.VerboseOpt, false, "Enable verbose logging") - if err := viper.BindPFlags(root.PersistentFlags()); err != nil { - log.Fatalf("Failed to bind root flags: %v", err) - } - - return root.Execute() -} - -// GetCLIRoot is intended to create the base command structure for the OSDK for use in CLI and documentation -// Deprecated: this is only used for projects without a PROJECT config/kubebuilder-style repo. -func GetCLIRoot() *cobra.Command { - root := &cobra.Command{ - Use: "operator-sdk", - Short: "An SDK for building operators with ease", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - if viper.GetBool(flags.VerboseOpt) { - 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 := checkGoModulesForCmd(cmd); err != nil { - log.Fatal(err) - } - }, - } - - root.AddCommand( - build.NewCmd(), - scorecard.NewCmd(), - completion.NewCmd(), - olm.NewCmd(), - version.NewCmd(), - ) - - return root -} - -func checkGoModulesForCmd(cmd *cobra.Command) (err error) { - // 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 they will not - // be using go modules. - if !projutil.IsOperatorGo() { - return nil - } - // Do not perform a go modules 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 - } - - return projutil.CheckGoModules() -} - -var commandsToSkip = map[string]struct{}{ - "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 - } - } - }) - return skip -} diff --git a/cmd/operator-sdk/generate/bundle/cmd.go b/cmd/operator-sdk/generate/bundle/cmd.go index b34adf46d2..897529c9e1 100644 --- a/cmd/operator-sdk/generate/bundle/cmd.go +++ b/cmd/operator-sdk/generate/bundle/cmd.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" + "github.com/operator-framework/operator-sdk/internal/util/projutil" ) //nolint:maligned @@ -68,7 +68,7 @@ func NewCmd() *cobra.Command { c.metadata = true } - cfg, err := kbutil.ReadConfig() + cfg, err := projutil.ReadConfig() if err != nil { return fmt.Errorf("error reading configuration: %v", err) } diff --git a/cmd/operator-sdk/generate/kustomize/manifests.go b/cmd/operator-sdk/generate/kustomize/manifests.go index d5fa7034e6..ab6d2b37f4 100644 --- a/cmd/operator-sdk/generate/kustomize/manifests.go +++ b/cmd/operator-sdk/generate/kustomize/manifests.go @@ -25,7 +25,6 @@ import ( gencsv "github.com/operator-framework/operator-sdk/internal/generate/clusterserviceversion" "github.com/operator-framework/operator-sdk/internal/plugins/util/kustomize" - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" "github.com/operator-framework/operator-sdk/internal/util/projutil" ) @@ -94,7 +93,7 @@ func newManifestsCmd() *cobra.Command { } } - cfg, err := kbutil.ReadConfig() + cfg, err := projutil.ReadConfig() if err != nil { return fmt.Errorf("error reading configuration: %v", err) } diff --git a/cmd/operator-sdk/generate/packagemanifests/cmd.go b/cmd/operator-sdk/generate/packagemanifests/cmd.go index 4eee7dbaf3..24bd611cfb 100644 --- a/cmd/operator-sdk/generate/packagemanifests/cmd.go +++ b/cmd/operator-sdk/generate/packagemanifests/cmd.go @@ -22,7 +22,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" + "github.com/operator-framework/operator-sdk/internal/util/projutil" ) //nolint:maligned @@ -59,7 +59,7 @@ func NewCmd() *cobra.Command { return fmt.Errorf("command %s doesn't accept any arguments", cmd.CommandPath()) } - cfg, err := kbutil.ReadConfig() + cfg, err := projutil.ReadConfig() if err != nil { log.Fatal(fmt.Errorf("error reading configuration: %v", err)) } diff --git a/cmd/operator-sdk/main.go b/cmd/operator-sdk/main.go index 8db6554d9e..ee195f0550 100644 --- a/cmd/operator-sdk/main.go +++ b/cmd/operator-sdk/main.go @@ -15,49 +15,16 @@ package main import ( - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that `exec-entrypoint` and `run` can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" - "github.com/operator-framework/operator-sdk/cmd/operator-sdk/cli" - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" - "github.com/operator-framework/operator-sdk/internal/util/projutil" - log "github.com/sirupsen/logrus" + + "github.com/operator-framework/operator-sdk/cmd/operator-sdk/cli" ) func main() { - // Use the new KB CLI when running inside a Kubebuilder project with an existing PROJECT file. - if kbutil.HasProjectFile() { - if err := cli.Run(); err != nil { - log.Fatal(err) - } - return - } - - // Use the legacy CLI if inside of a Go/Helm/Ansible legacy project - operatorType := projutil.GetOperatorType() - switch operatorType { - case projutil.OperatorTypeGo, projutil.OperatorTypeHelm, projutil.OperatorTypeAnsible: - // Deprecation warning for Go projects - // TODO/Discuss: UX wise, is displaying this notice on every command that runs - // in the legacy Go projects too loud. - if operatorType == projutil.OperatorTypeGo { - depMsg := "Operator SDK has a new CLI and project layout that is aligned with Kubebuilder.\n" + - "See `operator-sdk init -h` and the following doc on how to scaffold a new project:\n" + - "https://sdk.operatorframework.io/docs/golang/quickstart/\n" + - "To migrate existing projects to the new layout see:\n" + - "https://sdk.operatorframework.io/docs/golang/project_migration_guide/\n" - projutil.PrintDeprecationWarning(depMsg) - } - if err := cli.RunLegacy(); err != nil { - log.Fatal(err) - } - return - } - - // Run the KB CLI when not running in either legacy or new projects if err := cli.Run(); err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index 08d0fb8707..b773f2952d 100644 --- a/go.mod +++ b/go.mod @@ -12,14 +12,12 @@ require ( github.com/kr/text v0.1.0 github.com/markbates/inflect v1.0.4 github.com/mattn/go-isatty v0.0.12 - github.com/mitchellh/go-homedir v1.1.0 github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 github.com/operator-framework/api v0.3.8 github.com/operator-framework/operator-lib v0.0.0-20200724203809-f6728cc91ac6 github.com/operator-framework/operator-registry v1.12.6-0.20200611222234-275301b779f8 github.com/prometheus/client_golang v1.5.1 - github.com/rogpeppe/go-internal v1.5.0 github.com/sergi/go-diff v1.0.0 github.com/sirupsen/logrus v1.5.0 github.com/spf13/afero v1.2.2 diff --git a/go.sum b/go.sum index ca7dbb4ef5..3f38a5f18a 100644 --- a/go.sum +++ b/go.sum @@ -658,9 +658,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.5.0 h1:Usqs0/lDK/NqTkvrmKSwA/3XkZAs7ZAW/eLeQ2MVBTw= -github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3 h1:xkBtI5JktwbW/vf4vopBbhYsRFTGfQWHYXzC0/qYwxI= github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= diff --git a/internal/annotations/prefix.go b/internal/annotations/prefix.go deleted file mode 100644 index 5ee81d85b1..0000000000 --- a/internal/annotations/prefix.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package annotations - -import ( - "fmt" - "strings" -) - -const ( - SDKPrefix = "+operator-sdk" - - prefixSep = ":" - pathSep = "." - valueSep = "=" -) - -// joinWithTrim is like strings.Join() but trims sep from each element in elems -// prior to joining elems. Code modified from strings.Join(). -func joinWithTrim(sep string, elems ...string) string { - switch len(elems) { - case 0: - return "" - case 1: - return elems[0] - } - b := strings.Builder{} - b.WriteString(strings.Trim(elems[0], sep)) - for _, e := range elems[1:] { - if e = strings.Trim(e, sep); e != "" { - b.WriteString(sep) - b.WriteString(e) - } - } - return b.String() -} - -func JoinPrefix(tokens ...string) string { - return joinWithTrim(prefixSep, tokens...) -} - -func SplitPrefix(prefix string) ([]string, error) { - if !strings.Contains(prefix, prefixSep) { - return nil, fmt.Errorf(`prefix "%s" does not contain the prefix separator "%s"`, prefix, prefixSep) - } - split := strings.Split(prefix, prefixSep) - if len(split) == 0 || (len(split) == 1 && split[0] == "") { - return nil, fmt.Errorf(`prefix "%s" has no prefix tokens delimited by "%s"`, prefix, prefixSep) - } - if strings.TrimSpace(split[0]) != SDKPrefix { - return nil, fmt.Errorf(`prefix "%s" does not have SDK prefix "%s"`, prefix, SDKPrefix) - } - for i, p := range split { - if strings.TrimSpace(p) == "" { - return nil, fmt.Errorf(`prefix "%s" contains an empty token after colon index %d`, prefix, i) - } - } - return split, nil -} - -func JoinPath(elements ...string) string { - return joinWithTrim(pathSep, elements...) -} - -func SplitPath(path string) ([]string, error) { - if !strings.Contains(path, pathSep) { - return nil, fmt.Errorf(`path "%s" does not contain the path separator "%s"`, path, pathSep) - } - split := strings.Split(path, pathSep) - if len(split) == 0 || (len(split) == 1 && split[0] == "") { - return nil, fmt.Errorf(`path "%s" has no path elements delimited by "%s"`, path, pathSep) - } - for i, p := range split { - if strings.TrimSpace(p) == "" { - return nil, fmt.Errorf(`path "%s" contains an empty path element after dot index %d`, path, i) - } - } - return split, nil -} - -func JoinAnnotation(prefixedPath, value string) string { - return strings.Trim(prefixedPath, valueSep) + valueSep + strings.Trim(value, valueSep) -} - -func SplitAnnotation(annotation string) (prefixedPath, val string, err error) { - if !strings.Contains(annotation, valueSep) { - return "", "", fmt.Errorf(`annotation "%s" does not contain the value separator "%s"`, annotation, valueSep) - } - split := strings.Split(annotation, valueSep) - if len(split) != 2 { - return "", "", fmt.Errorf(`annotation "%s" does not have exactly one value separator "%s"`, annotation, valueSep) - } - if strings.TrimSpace(split[0]) == "" { - return "", "", fmt.Errorf(`annotation "%s" contains an empty annotation component`, annotation) - } - if strings.TrimSpace(split[1]) == "" { - return "", "", fmt.Errorf(`annotation "%s" contains an empty value component`, annotation) - } - return split[0], split[1], nil -} diff --git a/internal/annotations/prefix_test.go b/internal/annotations/prefix_test.go deleted file mode 100644 index ad11577a24..0000000000 --- a/internal/annotations/prefix_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package annotations - -import ( - "testing" -) - -func TestSplitPrefix(t *testing.T) { - cases := []struct { - name string - prefix string - result []string - wantError bool - }{ - {"empty", "", nil, true}, - {"no prefix separator or use case prefix", "+operator-sdk", nil, true}, - {"no use case prefix", "+operator-sdk:", nil, true}, - {"use case prefix", "+operator-sdk:foo", []string{"foo"}, false}, - {"use case prefix and one path token", "+operator-sdk:foo:bar", []string{"foo", "bar"}, false}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got, err := SplitPrefix(c.prefix) - if err != nil && !c.wantError { - t.Errorf("Wanted result %+q, got error: %v", c.result, err) - } else if err == nil && c.wantError { - t.Errorf("Wanted error, got result %+q", got) - } - }) - } -} - -func TestSplitPath(t *testing.T) { - cases := []struct { - name string - path string - result []string - wantError bool - }{ - {"empty", "", nil, true}, - {"no prefix separator or use case prefix", "+operator-sdk", nil, true}, - {"use case prefix", "+operator-sdk:foo", nil, true}, - {"use case prefix and path element with empty child path element", "+operator-sdk:foo:bar.", nil, true}, - {"use case prefix and path elements", "+operator-sdk:foo:bar.baz", []string{"+operator-sdk:foo:bar", "baz"}, false}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got, err := SplitPath(c.path) - if err != nil && !c.wantError { - t.Errorf("Wanted result %+q, got error: %v", c.result, err) - } else if err == nil && c.wantError { - t.Errorf("Wanted error, got result %+q", got) - } - }) - } -} - -func TestSplitAnnotation(t *testing.T) { - cases := []struct { - name string - annotation string - path, val string - wantError bool - }{ - {"empty", "", "", "", true}, - {"no prefix separator or use case prefix", "+operator-sdk", "", "", true}, - {"prefixed path with empty value", "+operator-sdk:foo:bar.baz=", "", "", true}, - {"prefixed path with value", "+operator-sdk:foo:bar.baz=value", "+operator-sdk:foo:bar.baz", "value", false}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - gotPath, gotVal, err := SplitAnnotation(c.annotation) - if err != nil && !c.wantError { - t.Errorf("Wanted path %s and val %s, got error: %v", c.path, c.val, err) - } else if err == nil && c.wantError { - t.Errorf("Wanted error, got path %s and val %s", gotPath, gotVal) - } - }) - } -} diff --git a/internal/generate/clusterserviceversion/clusterserviceversion_test.go b/internal/generate/clusterserviceversion/clusterserviceversion_test.go index d9814dba46..f6ab7ddefe 100644 --- a/internal/generate/clusterserviceversion/clusterserviceversion_test.go +++ b/internal/generate/clusterserviceversion/clusterserviceversion_test.go @@ -39,7 +39,6 @@ import ( metricsannotations "github.com/operator-framework/operator-sdk/internal/annotations/metrics" "github.com/operator-framework/operator-sdk/internal/generate/collector" genutil "github.com/operator-framework/operator-sdk/internal/generate/internal" - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" "github.com/operator-framework/operator-sdk/internal/util/projutil" ) @@ -394,7 +393,7 @@ func readConfigHelper(dir string) *config.Config { wd, err := os.Getwd() ExpectWithOffset(1, err).ToNot(HaveOccurred()) ExpectWithOffset(1, os.Chdir(dir)).ToNot(HaveOccurred()) - cfg, err := kbutil.ReadConfig() + cfg, err := projutil.ReadConfig() ExpectWithOffset(1, err).ToNot(HaveOccurred()) ExpectWithOffset(1, os.Chdir(wd)).ToNot(HaveOccurred()) return cfg diff --git a/internal/scaffold/constants.go b/internal/scaffold/constants.go deleted file mode 100644 index c6ff3eb3a9..0000000000 --- a/internal/scaffold/constants.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "path/filepath" -) - -const ( - // Separator to statically create directories. - filePathSep = string(filepath.Separator) - - // dirs - CmdDir = "cmd" - ManagerDir = CmdDir + filePathSep + "manager" - PkgDir = "pkg" - ApisDir = PkgDir + filePathSep + "apis" - ControllerDir = PkgDir + filePathSep + "controller" - BuildDir = "build" - BuildBinDir = BuildDir + filePathSep + "_output" + filePathSep + "bin" - DeployDir = "deploy" - CRDsDir = DeployDir + filePathSep + "crds" - - // files - DockerfileFile = "Dockerfile" - OperatorYamlFile = "operator.yaml" -) diff --git a/internal/scaffold/cr.go b/internal/scaffold/cr.go deleted file mode 100644 index f8a7880108..0000000000 --- a/internal/scaffold/cr.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "fmt" - "path/filepath" - "strings" - "text/template" - - "github.com/operator-framework/operator-sdk/internal/scaffold/input" -) - -// CR is the input needed to generate a deploy/crds/___cr.yaml file -type CR struct { - input.Input - - // Resource defines the inputs for the new custom resource - Resource *Resource - - // Spec is a custom spec for the CR. It will be automatically indented. If - // unset, a default spec will be created. - Spec string -} - -func (s *CR) GetInput() (input.Input, error) { - if s.Path == "" { - s.Path = crPathForResource(CRDsDir, s.Resource) - } - s.TemplateBody = crTemplate - if s.TemplateFuncs == nil { - s.TemplateFuncs = template.FuncMap{} - } - s.TemplateFuncs["indent"] = indent - return s.Input, nil -} - -func crPathForResource(dir string, r *Resource) string { - file := fmt.Sprintf("%s_%s_%s_cr.yaml", r.FullGroup, r.Version, r.LowerKind) - return filepath.Join(dir, file) -} - -func indent(spaces int, v string) string { - pad := strings.Repeat(" ", spaces) - return pad + strings.Replace(v, "\n", "\n"+pad, -1) -} - -const crTemplate = `apiVersion: {{ .Resource.APIVersion }} -kind: {{ .Resource.Kind }} -metadata: - name: example-{{ .Resource.LowerKind }} -spec: -{{- with .Spec }} -{{ . | indent 2 }} -{{- else }} - # Add fields here - size: 3 -{{- end }} -` diff --git a/internal/scaffold/cr_test.go b/internal/scaffold/cr_test.go deleted file mode 100644 index ae79bf25c5..0000000000 --- a/internal/scaffold/cr_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "testing" - - "github.com/operator-framework/operator-sdk/internal/util/diffutil" -) - -func TestCR(t *testing.T) { - r, err := NewResource(appAPIVersion, appKind) - if err != nil { - t.Fatal(err) - } - s, buf := setupScaffoldAndWriter() - err = s.Execute(appConfig, &CR{Resource: r}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if crExp != buf.String() { - diffs := diffutil.Diff(crExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -func TestCRCustomSpec(t *testing.T) { - r, err := NewResource(appAPIVersion, appKind) - if err != nil { - t.Fatal(err) - } - s, buf := setupScaffoldAndWriter() - err = s.Execute(appConfig, &CR{ - Resource: r, - Spec: "# Custom spec here\ncustomSize: 6", - }) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if crCustomSpecExp != buf.String() { - diffs := diffutil.Diff(crCustomSpecExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -const crExp = `apiVersion: app.example.com/v1alpha1 -kind: AppService -metadata: - name: example-appservice -spec: - # Add fields here - size: 3 -` - -const crCustomSpecExp = `apiVersion: app.example.com/v1alpha1 -kind: AppService -metadata: - name: example-appservice -spec: - # Custom spec here - customSize: 6 -` diff --git a/internal/scaffold/customrender.go b/internal/scaffold/customrender.go deleted file mode 100644 index 5d3bfc65ca..0000000000 --- a/internal/scaffold/customrender.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import "github.com/spf13/afero" - -// CustomRenderer is the interface for writing any scaffold file that does -// not use a template. -type CustomRenderer interface { - // SetFS sets the fs in the CustomRenderer's underlying type if it exists. - // SetFS is used to inject the callers' fs into a CustomRenderer, which may - // want to write/read from the same fs. - SetFS(afero.Fs) - // CustomRender performs arbitrary rendering of file data and returns - // bytes to write to a file. - CustomRender() ([]byte, error) -} diff --git a/internal/scaffold/input/input.go b/internal/scaffold/input/input.go deleted file mode 100644 index 6d543cd368..0000000000 --- a/internal/scaffold/input/input.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Modified from github.com/kubernetes-sigs/controller-tools/pkg/scaffold/input/input.go - -package input - -import "text/template" - -// IfExistsAction determines what to do if the scaffold file already exists -type IfExistsAction int - -const ( - // Overwrite truncates and overwrites the existing file (default) - Overwrite IfExistsAction = iota - - // Error returns an error and stops processing - Error - - // Skip skips the file and moves to the next one - Skip -) - -// Input is the input for scaffoldig a file -type Input struct { - // Path is the file to write - Path string - - // IfExistsAction determines what to do if the file exists - IfExistsAction IfExistsAction - - // IsExec indicates whether the file should be written with executable - // permissions. - // Defaults to false - IsExec bool - - // TemplateBody is the template body to execute - TemplateBody string - - // TemplateFuncs are any funcs used in the template. These funcs must be - // registered before execution. - TemplateFuncs template.FuncMap - - // Repo is the go project package - Repo string - - // AbsProjectPath is the absolute path to the project root, including the project directory. - AbsProjectPath string - - // ProjectName is the operator's name, ex. app-operator - ProjectName string - - // Delims is a slice of two strings representing the left and right delimiter - // defaults to {{ }} - Delims [2]string -} - -// Repo allows a repo to be set on an object -type Repo interface { - // SetRepo sets the repo - SetRepo(string) -} - -// SetRepo sets the repo -func (i *Input) SetRepo(r string) { - if i.Repo == "" { - i.Repo = r - } -} - -// AbsProjectPath allows the absolute project path to be set on an object -type AbsProjectPath interface { - // SetAbsProjectPath sets the project file location - SetAbsProjectPath(string) -} - -// SetAbsProjectPath sets the absolute project path -func (i *Input) SetAbsProjectPath(p string) { - if i.AbsProjectPath == "" { - i.AbsProjectPath = p - } -} - -// ProjectName allows the project name to be set on an object -type ProjectName interface { - // SetProjectName sets the project name - SetProjectName(string) -} - -// SetProjectName sets the project name -func (i *Input) SetProjectName(n string) { - if i.ProjectName == "" { - i.ProjectName = n - } -} - -// File is a scaffoldable file -type File interface { - // GetInput returns the Input for creating a scaffold file - GetInput() (Input, error) -} - -// Validate validates input -type Validate interface { - // Validate returns nil if the inputs' validation logic approves of - // field values, the template, etc. - Validate() error -} - -// Config configures the execution scaffold templates -type Config struct { - // Repo is the go project package - Repo string - - // AbsProjectPath is the absolute path to the project root, including the project directory. - AbsProjectPath string - - // ProjectName is the operator's name, ex. app-operator - ProjectName string -} diff --git a/internal/scaffold/internal/deps/print_go_mod.go b/internal/scaffold/internal/deps/print_go_mod.go deleted file mode 100644 index 9dc4eb3a71..0000000000 --- a/internal/scaffold/internal/deps/print_go_mod.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package deps - -import ( - "bytes" - "fmt" - "html/template" - - "github.com/operator-framework/operator-sdk/internal/util/projutil" -) - -func ExecGoModTmpl(tmpl string) ([]byte, error) { - projutil.MustInProjectRoot() - repo := projutil.GetGoPkg() - t, err := template.New("").Parse(tmpl) - if err != nil { - return nil, fmt.Errorf("failed to parse go mod template: %v", err) - } - buf := &bytes.Buffer{} - if err := t.Execute(buf, struct{ Repo string }{Repo: repo}); err != nil { - return nil, fmt.Errorf("failed to execute go mod template: %v", err) - } - return buf.Bytes(), nil -} diff --git a/internal/scaffold/internal/testutil/test_util.go b/internal/scaffold/internal/testutil/test_util.go deleted file mode 100644 index 6c4f07780b..0000000000 --- a/internal/scaffold/internal/testutil/test_util.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test - -import ( - "os" - - "github.com/operator-framework/operator-sdk/internal/util/fileutil" - - "github.com/spf13/afero" -) - -func WriteOSPathToFS(fromFS, toFS afero.Fs, root string) error { - if _, err := fromFS.Stat(root); err != nil { - return err - } - return afero.Walk(fromFS, root, func(path string, info os.FileInfo, err error) error { - if err != nil || info == nil { - return err - } - // only copy non-dir and non-symlink files - if !info.IsDir() && info.Mode()&os.ModeSymlink == 0 { - b, err := afero.ReadFile(fromFS, path) - if err != nil { - return err - } - return afero.WriteFile(toFS, path, b, fileutil.DefaultFileMode) - } - return nil - }) -} diff --git a/internal/scaffold/resource.go b/internal/scaffold/resource.go deleted file mode 100644 index d2d556051c..0000000000 --- a/internal/scaffold/resource.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Modified from github.com/kubernetes-sigs/controller-tools/pkg/scaffold/resource/resource.go - -package scaffold - -import ( - "errors" - "fmt" - "regexp" - "strings" - - "github.com/markbates/inflect" - "k8s.io/apimachinery/pkg/util/validation" -) - -var ( - // ResourceVersionRegexp matches Kubernetes API versions. - // See https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning - ResourceVersionRegexp = regexp.MustCompile("^v[1-9][0-9]*((alpha|beta)[1-9][0-9]*)?$") - // ResourceKindRegexp matches Kubernetes API Kind's. - ResourceKindRegexp = regexp.MustCompile("^[A-Z]{1}[a-zA-Z0-9]+$") -) - -// Resource contains the information required to scaffold files for a resource. -type Resource struct { - // APIVersion is the complete group-subdomain/version e.g app.example.com/v1alpha1 - APIVersion string - - // Kind is the API Kind e.g AppService - Kind string - - // FullGroup is the complete group name with subdomain e.g app.example.com - // Parsed from APIVersion - FullGroup string - - // Group is the API Group. Does not contain the sub-domain. e.g app - // Parsed from APIVersion - Group string - - // GoImportGroup is the non-hyphenated go import group for this resource - GoImportGroup string - - // Version is the API version - e.g. v1alpha1 - // Parsed from APIVersion - Version string - - // Resource is the API Resource i.e plural(lowercased(Kind)) e.g appservices - Resource string - - // LowerKind is lowercased(Kind) e.g appservice - LowerKind string - - // TODO: allow user to specify list of short names for Resource e.g app, myapp -} - -func NewResource(apiVersion, kind string) (*Resource, error) { - r := &Resource{ - APIVersion: apiVersion, - Kind: kind, - } - if err := r.Validate(); err != nil { - return nil, err - } - return r, nil -} - -// Validate defaults and checks the Resource values to make sure they are valid. -func (r *Resource) Validate() error { - if len(r.APIVersion) == 0 { - return errors.New("api-version cannot be empty") - } - - if err := r.checkAndSetKinds(); err != nil { - return err - } - if err := r.checkAndSetGroups(); err != nil { - return err - } - if err := r.checkAndSetVersion(); err != nil { - return err - } - - rs := inflect.NewDefaultRuleset() - if len(r.Resource) == 0 { - r.Resource = rs.Pluralize(strings.ToLower(r.Kind)) - } - - return nil -} - -func (r *Resource) checkAndSetKinds() error { - if len(r.Kind) == 0 { - return errors.New("kind cannot be empty") - } - - r.LowerKind = strings.ToLower(r.Kind) - - if strings.Title(r.Kind) != r.Kind { - return fmt.Errorf("kind must begin with uppercase (was %v)", r.Kind) - } - if !ResourceKindRegexp.MatchString(r.Kind) { - return errors.New("kind should consist of lower and uppercase alphabetical characters") - } - return nil -} - -func (r *Resource) checkAndSetGroups() error { - fg := strings.Split(r.APIVersion, "/") - if len(fg) < 2 || len(fg[0]) == 0 { - return errors.New("full group cannot be empty") - } - g := strings.Split(fg[0], ".") - if len(g) == 0 || len(g[0]) == 0 { - return errors.New("group cannot be empty") - } - r.FullGroup = fg[0] - r.Group = g[0] - - s := strings.ToLower(r.Group) - r.GoImportGroup = strings.Replace(s, "-", "", -1) - - if err := validation.IsDNS1123Subdomain(r.Group); err != nil { - return fmt.Errorf("group name is invalid: %v", err) - } - return nil -} - -func (r *Resource) checkAndSetVersion() error { - api := strings.Split(r.APIVersion, "/") - if len(api) < 2 || len(api[1]) == 0 { - return errors.New("version cannot be empty") - } - r.Version = api[1] - - if !ResourceVersionRegexp.MatchString(r.Version) { - return errors.New("version is not in the correct Kubernetes version format, ex. v1alpha1") - } - return nil -} diff --git a/internal/scaffold/role.go b/internal/scaffold/role.go deleted file mode 100644 index 595c6aae83..0000000000 --- a/internal/scaffold/role.go +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "errors" - "fmt" - "io/ioutil" - "path/filepath" - "sort" - - log "github.com/sirupsen/logrus" - rbacv1 "k8s.io/api/rbac/v1" - cgoscheme "k8s.io/client-go/kubernetes/scheme" - yaml "sigs.k8s.io/yaml" - - "github.com/operator-framework/operator-sdk/internal/scaffold/input" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" -) - -const RoleYamlFile = "role.yaml" - -type Role struct { - input.Input - - IsClusterScoped bool - SkipDefaultRules bool - CustomRules []rbacv1.PolicyRule -} - -func (s *Role) GetInput() (input.Input, error) { - if s.Path == "" { - s.Path = filepath.Join(DeployDir, RoleYamlFile) - } - s.TemplateBody = roleTemplate - return s.Input, nil -} - -func UpdateRoleForResource(r *Resource, absProjectPath string) error { - // append rbac rule to deploy/role.yaml - roleFilePath := filepath.Join(absProjectPath, DeployDir, RoleYamlFile) - roleYAML, err := ioutil.ReadFile(roleFilePath) - if err != nil { - return fmt.Errorf("failed to read role manifest %v: %v", roleFilePath, err) - } - obj, _, err := cgoscheme.Codecs.UniversalDeserializer().Decode(roleYAML, nil, nil) - if err != nil { - return fmt.Errorf("failed to decode role manifest %v: %v", roleFilePath, err) - } - switch role := obj.(type) { - case *rbacv1.Role: - pr := &rbacv1.PolicyRule{} - - apiGroupFound := false - for i := range role.Rules { - if role.Rules[i].APIGroups[0] == r.FullGroup { - apiGroupFound = true - pr = &role.Rules[i] - break - } - } - - // check if the resource already exists - for _, resource := range pr.Resources { - if resource == r.Resource { - log.Infof("RBAC rules in deploy/role.yaml already up to date for the resource (%v, %v)", r.APIVersion, r.Kind) - return nil - } - } - - pr.Resources = append(pr.Resources, r.Resource) - // create a new apiGroup if not found. - if !apiGroupFound { - pr.APIGroups = []string{r.FullGroup} - // Using "*" to allow access to the resource and all its subresources e.g "memcacheds" and "memcacheds/finalizers" - // https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement - pr.Resources = []string{"*"} - pr.Verbs = []string{ - "create", - "delete", - "get", - "list", - "patch", - "update", - "watch", - } - role.Rules = append(role.Rules, *pr) - } - - return updateRoleFile(&role, roleFilePath) - case *rbacv1.ClusterRole: - pr := &rbacv1.PolicyRule{} - apiGroupFound := false - for i := range role.Rules { - if role.Rules[i].APIGroups[0] == r.FullGroup { - apiGroupFound = true - pr = &role.Rules[i] - break - } - } - // check if the resource already exists - for _, resource := range pr.Resources { - if resource == r.Resource { - log.Infof("RBAC rules in deploy/role.yaml already up to date for the resource (%v, %v)", r.APIVersion, r.Kind) - return nil - } - } - - pr.Resources = append(pr.Resources, r.Resource) - // create a new apiGroup if not found. - if !apiGroupFound { - pr.APIGroups = []string{r.FullGroup} - // Using "*" to allow access to the resource and all its subresources e.g "memcacheds" and "memcacheds/finalizers" - // https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement - pr.Resources = []string{"*"} - pr.Verbs = []string{ - "create", - "delete", - "get", - "list", - "patch", - "update", - "watch", - } - role.Rules = append(role.Rules, *pr) - } - - return updateRoleFile(&role, roleFilePath) - default: - return errors.New("failed to parse role.yaml as a role") - } -} - -// MergeRoleForResource merges incoming new API resource rules with existing deploy/role.yaml -func MergeRoleForResource(r *Resource, absProjectPath string, roleScaffold Role) error { - roleFilePath := filepath.Join(absProjectPath, DeployDir, RoleYamlFile) - roleYAML, err := ioutil.ReadFile(roleFilePath) - if err != nil { - return fmt.Errorf("failed to read role manifest %v: %v", roleFilePath, err) - } - // Check for existing role.yaml - if len(roleYAML) == 0 { - return fmt.Errorf("empty Role File at: %v", absProjectPath) - } - // Check for incoming new Role - if len(roleScaffold.CustomRules) == 0 { - return fmt.Errorf("customRules cannot be empty for new Role at: %v", r.APIVersion) - } - - obj, _, err := cgoscheme.Codecs.UniversalDeserializer().Decode(roleYAML, nil, nil) - if err != nil { - return fmt.Errorf("failed to decode role manifest %v: %v", roleFilePath, err) - } - switch role := obj.(type) { - case *rbacv1.Role: - // TODO: Add logic to merge Cluster scoped rules into existing Kind: Role scoped rules - // Error out for ClusterRole merging with existing Kind: Role - if roleScaffold.IsClusterScoped { - return fmt.Errorf("cannot Merge Cluster scoped rules with existing deploy/role.yaml. " + - "please modify existing deploy/role.yaml and deploy/role_binding.yaml " + - "to reflect Cluster scope and try again") - } - mergedRoleRules := mergeRules(role.Rules, roleScaffold) - role.Rules = mergedRoleRules - case *rbacv1.ClusterRole: - mergedClusterRoleRules := mergeRules(role.Rules, roleScaffold) - role.Rules = mergedClusterRoleRules - default: - log.Errorf("Failed to parse role.yaml as a role %v", err) - } - - if err := updateRoleFile(obj, roleFilePath); err != nil { - return fmt.Errorf("failed to update for resource (%v, %v): %v", - r.APIVersion, r.Kind, err) - } - - return UpdateRoleForResource(r, absProjectPath) -} - -func ifMatches(pr []string, newPr []string) bool { - - sort.Strings(pr) - sort.Strings(newPr) - - if len(pr) != len(newPr) { - return false - } - for i, v := range pr { - if v != newPr[i] { - return false - } - } - return true -} - -func findResource(resources []string, search string) bool { - for _, r := range resources { - if r == search || r == "*" { - return true - } - } - return false -} - -func mergeRules(rules1 []rbacv1.PolicyRule, rules2 Role) []rbacv1.PolicyRule { - for j := range rules2.CustomRules { - ruleFound := false - prj := &rules2.CustomRules[j] - iLoop: - for i, pri := range rules1 { - // check if apiGroup, verbs, resourceName, and nonResourceURLS matches for new resource. - apiGroupsEqual := ifMatches(pri.APIGroups, prj.APIGroups) - verbsEqual := ifMatches(pri.Verbs, prj.Verbs) - resourceNamesEqual := ifMatches(pri.ResourceNames, prj.ResourceNames) - nonResourceURLsEqual := ifMatches(pri.NonResourceURLs, prj.NonResourceURLs) - - if apiGroupsEqual && verbsEqual && resourceNamesEqual && nonResourceURLsEqual { - for _, newResource := range prj.Resources { - if !findResource(pri.Resources, newResource) { - // append rbac rule to deploy/role.yaml - rules1[i].Resources = append(rules1[i].Resources, newResource) - } - } - ruleFound = true - break iLoop - } - } - if !ruleFound { - rules1 = append(rules1, *prj) - } - } - return rules1 -} -func updateRoleFile(role interface{}, roleFilePath string) error { - data, err := yaml.Marshal(&role) - if err != nil { - return fmt.Errorf("failed to marshal role(%+v): %v", role, err) - } - if err := ioutil.WriteFile(roleFilePath, data, fileutil.DefaultFileMode); err != nil { - return fmt.Errorf("failed to update %v: %v", roleFilePath, err) - } - - return nil -} - -const roleTemplate = `kind: {{if .IsClusterScoped}}Cluster{{end}}Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{.ProjectName}} -rules: -{{- if not .SkipDefaultRules }} -- apiGroups: - - "" - resources: - - pods - - services - - services/finalizers - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -{{- end }} -{{- range .CustomRules }} -- verbs: - {{- range .Verbs }} - - "{{ . }}" - {{- end }} - {{- with .APIGroups }} - apiGroups: - {{- range . }} - - "{{ . }}" - {{- end }} - {{- end }} - {{- with .Resources }} - resources: - {{- range . }} - - "{{ . }}" - {{- end }} - {{- end }} - {{- with .ResourceNames }} - resourceNames: - {{- range . }} - - "{{ . }}" - {{- end }} - {{- end }} - {{- with .NonResourceURLs }} - nonResourceURLs: - {{- range . }} - - "{{ . }}" - {{- end }} - {{- end }} -{{- end }} -` diff --git a/internal/scaffold/role_test.go b/internal/scaffold/role_test.go deleted file mode 100644 index d58204159f..0000000000 --- a/internal/scaffold/role_test.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "testing" - - "github.com/operator-framework/operator-sdk/internal/util/diffutil" - rbacv1 "k8s.io/api/rbac/v1" -) - -func TestRole(t *testing.T) { - s, buf := setupScaffoldAndWriter() - err := s.Execute(appConfig, &Role{}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if roleExp != buf.String() { - diffs := diffutil.Diff(roleExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -func TestRoleClusterScoped(t *testing.T) { - s, buf := setupScaffoldAndWriter() - err := s.Execute(appConfig, &Role{IsClusterScoped: true}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if clusterroleExp != buf.String() { - diffs := diffutil.Diff(clusterroleExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -func TestRoleCustomRules(t *testing.T) { - s, buf := setupScaffoldAndWriter() - err := s.Execute(appConfig, &Role{ - SkipDefaultRules: true, - CustomRules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"policy"}, - Resources: []string{"poddisruptionbudgets"}, - Verbs: []string{ - "create", - "delete", - "get", - "list", - "patch", - "update", - "watch", - }, - }, - { - APIGroups: []string{"rbac.authorization.k8s.io"}, - Resources: []string{"roles", "rolebindings"}, - Verbs: []string{"get", "list", "watch"}, - }, - }}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if roleCustomRulesExp != buf.String() { - diffs := diffutil.Diff(roleCustomRulesExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -const roleExp = `kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: app-operator -rules: -- apiGroups: - - "" - resources: - - pods - - services - - services/finalizers - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -` - -const clusterroleExp = `kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: app-operator -rules: -- apiGroups: - - "" - resources: - - pods - - services - - services/finalizers - - endpoints - - persistentvolumeclaims - - events - - configmaps - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -` - -const roleCustomRulesExp = `kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: app-operator -rules: -- verbs: - - "create" - - "delete" - - "get" - - "list" - - "patch" - - "update" - - "watch" - apiGroups: - - "policy" - resources: - - "poddisruptionbudgets" -- verbs: - - "get" - - "list" - - "watch" - apiGroups: - - "rbac.authorization.k8s.io" - resources: - - "roles" - - "rolebindings" -` diff --git a/internal/scaffold/rolebinding.go b/internal/scaffold/rolebinding.go deleted file mode 100644 index 8edfe9bdce..0000000000 --- a/internal/scaffold/rolebinding.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "path/filepath" - - "github.com/operator-framework/operator-sdk/internal/scaffold/input" -) - -const RoleBindingYamlFile = "role_binding.yaml" - -type RoleBinding struct { - input.Input - - IsClusterScoped bool -} - -func (s *RoleBinding) GetInput() (input.Input, error) { - if s.Path == "" { - s.Path = filepath.Join(DeployDir, RoleBindingYamlFile) - } - s.TemplateBody = roleBindingTemplate - return s.Input, nil -} - -const roleBindingTemplate = `kind: {{if .IsClusterScoped}}Cluster{{end}}RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{.ProjectName}} -subjects: -- kind: ServiceAccount - name: {{.ProjectName}} - {{- if .IsClusterScoped }} - # Replace this with the namespace the operator is deployed in. - namespace: REPLACE_NAMESPACE - {{- end }} -roleRef: - kind: {{if .IsClusterScoped}}Cluster{{end}}Role - name: {{.ProjectName}} - apiGroup: rbac.authorization.k8s.io -` diff --git a/internal/scaffold/rolebinding_test.go b/internal/scaffold/rolebinding_test.go deleted file mode 100644 index ef8a639a55..0000000000 --- a/internal/scaffold/rolebinding_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "testing" - - "github.com/operator-framework/operator-sdk/internal/util/diffutil" -) - -func TestRoleBinding(t *testing.T) { - s, buf := setupScaffoldAndWriter() - err := s.Execute(appConfig, &RoleBinding{}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if rolebindingExp != buf.String() { - diffs := diffutil.Diff(rolebindingExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -func TestRoleBindingClusterScoped(t *testing.T) { - s, buf := setupScaffoldAndWriter() - err := s.Execute(appConfig, &RoleBinding{IsClusterScoped: true}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if clusterrolebindingExp != buf.String() { - diffs := diffutil.Diff(clusterrolebindingExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -const rolebindingExp = `kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: app-operator -subjects: -- kind: ServiceAccount - name: app-operator -roleRef: - kind: Role - name: app-operator - apiGroup: rbac.authorization.k8s.io -` - -const clusterrolebindingExp = `kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: app-operator -subjects: -- kind: ServiceAccount - name: app-operator - # Replace this with the namespace the operator is deployed in. - namespace: REPLACE_NAMESPACE -roleRef: - kind: ClusterRole - name: app-operator - apiGroup: rbac.authorization.k8s.io -` diff --git a/internal/scaffold/scaffold.go b/internal/scaffold/scaffold.go deleted file mode 100644 index 5563fdd045..0000000000 --- a/internal/scaffold/scaffold.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Modified from github.com/kubernetes-sigs/controller-tools/pkg/scaffold/scaffold.go - -package scaffold - -import ( - "bytes" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "text/template" - - log "github.com/sirupsen/logrus" - "github.com/spf13/afero" - "golang.org/x/tools/imports" - - "github.com/operator-framework/operator-sdk/internal/scaffold/input" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" -) - -// Scaffold writes Templates to scaffold new files -type Scaffold struct { - // Repo is the go project package - Repo string - // AbsProjectPath is the absolute path to the project root, including the project directory. - AbsProjectPath string - // ProjectName is the operator's name, ex. app-operator - ProjectName string - // Fs is the filesystem GetWriter uses to write scaffold files. - Fs afero.Fs - // GetWriter returns a writer for writing scaffold files. - GetWriter func(path string, mode os.FileMode) (io.Writer, error) -} - -func (s *Scaffold) setFieldsAndValidate(t input.File) error { - if b, ok := t.(input.Repo); ok { - b.SetRepo(s.Repo) - } - if b, ok := t.(input.AbsProjectPath); ok { - b.SetAbsProjectPath(s.AbsProjectPath) - } - if b, ok := t.(input.ProjectName); ok { - b.SetProjectName(s.ProjectName) - } - - // Validate the template is ok - if v, ok := t.(input.Validate); ok { - if err := v.Validate(); err != nil { - return err - } - } - return nil -} - -func (s *Scaffold) configure(cfg *input.Config) { - s.Repo = cfg.Repo - s.AbsProjectPath = cfg.AbsProjectPath - s.ProjectName = cfg.ProjectName -} - -// Execute executes scaffolding the Files -func (s *Scaffold) Execute(cfg *input.Config, files ...input.File) error { - if s.Fs == nil { - s.Fs = afero.NewOsFs() - } - if s.GetWriter == nil { - s.GetWriter = fileutil.NewFileWriterFS(s.Fs).WriteCloser - } - - // Configure s using common fields from cfg. - s.configure(cfg) - - for _, f := range files { - if err := s.doFile(f); err != nil { - return err - } - } - return nil -} - -// doFile scaffolds a single file -func (s *Scaffold) doFile(e input.File) error { - // Set common fields - err := s.setFieldsAndValidate(e) - if err != nil { - return err - } - - // Get the template input params - i, err := e.GetInput() - if err != nil { - return err - } - - // Ensure we use the absolute file path; i.Path is relative to the project root. - absFilePath := filepath.Join(s.AbsProjectPath, i.Path) - - // Check if the file to write already exists - if _, err := s.Fs.Stat(absFilePath); err == nil || os.IsExist(err) { - switch i.IfExistsAction { - case input.Overwrite: - case input.Skip: - return nil - case input.Error: - return fmt.Errorf("%s already exists", absFilePath) - } - } - - return s.doRender(i, e, absFilePath) -} - -const goFileExt = ".go" - -func isGoFile(p string) bool { - return filepath.Ext(p) == goFileExt -} - -func (s *Scaffold) doRender(i input.Input, e input.File, absPath string) error { - var mode os.FileMode = fileutil.DefaultFileMode - if i.IsExec { - mode = fileutil.DefaultExecFileMode - } - f, err := s.GetWriter(absPath, mode) - if err != nil { - return err - } - if c, ok := f.(io.Closer); ok { - defer func() { - if err := c.Close(); err != nil { - log.Fatal(err) - } - }() - } - - var b []byte - if c, ok := e.(CustomRenderer); ok { - c.SetFS(s.Fs) - // CustomRenderers have a non-template method of file rendering. - if b, err = c.CustomRender(); err != nil { - return err - } - } else { - // All other files are rendered via their templates. - temp, err := newTemplate(i) - if err != nil { - return err - } - - out := &bytes.Buffer{} - if err = temp.Execute(out, e); err != nil { - return err - } - b = out.Bytes() - } - - // gofmt the imports - if isGoFile(absPath) { - b, err = imports.Process(absPath, b, nil) - if err != nil { - return err - } - } - - // Files being overwritten must be trucated to len 0 so no old bytes remain. - if _, err = s.Fs.Stat(absPath); err == nil && i.IfExistsAction == input.Overwrite { - if file, ok := f.(afero.File); ok { - if err = file.Truncate(0); err != nil { - return err - } - } - } - - _, err = f.Write(b) - log.Infoln("Created", i.Path) - return err -} - -// newTemplate returns a new template named by i.Path with common functions and -// the input's TemplateFuncs. -func newTemplate(i input.Input) (*template.Template, error) { - t := template.New(i.Path).Funcs(template.FuncMap{ - "title": strings.Title, - "lower": strings.ToLower, - }) - if len(i.TemplateFuncs) > 0 { - t.Funcs(i.TemplateFuncs) - } - if i.Delims[0] != "" && i.Delims[1] != "" { - t.Delims(i.Delims[0], i.Delims[1]) - } - return t.Parse(i.TemplateBody) -} diff --git a/internal/scaffold/service_account.go b/internal/scaffold/service_account.go deleted file mode 100644 index 0219df655c..0000000000 --- a/internal/scaffold/service_account.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "path/filepath" - - "github.com/operator-framework/operator-sdk/internal/scaffold/input" -) - -const ServiceAccountYamlFile = "service_account.yaml" - -type ServiceAccount struct { - input.Input -} - -func (s *ServiceAccount) GetInput() (input.Input, error) { - if s.Path == "" { - s.Path = filepath.Join(DeployDir, ServiceAccountYamlFile) - } - s.TemplateBody = serviceAccountTemplate - return s.Input, nil -} - -const serviceAccountTemplate = `apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{.ProjectName}} -` diff --git a/internal/scaffold/service_account_test.go b/internal/scaffold/service_account_test.go deleted file mode 100644 index b3b9a672e1..0000000000 --- a/internal/scaffold/service_account_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "testing" - - "github.com/operator-framework/operator-sdk/internal/util/diffutil" -) - -func TestServiceAccount(t *testing.T) { - s, buf := setupScaffoldAndWriter() - err := s.Execute(appConfig, &ServiceAccount{}) - if err != nil { - t.Fatalf("Failed to execute the scaffold: (%v)", err) - } - - if serviceAccountExp != buf.String() { - diffs := diffutil.Diff(serviceAccountExp, buf.String()) - t.Fatalf("Expected vs actual differs.\n%v", diffs) - } -} - -const serviceAccountExp = `apiVersion: v1 -kind: ServiceAccount -metadata: - name: app-operator -` diff --git a/internal/scaffold/test_setup.go b/internal/scaffold/test_setup.go deleted file mode 100644 index bfc1f19dc5..0000000000 --- a/internal/scaffold/test_setup.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scaffold - -import ( - "bytes" - "io" - "os" - "path/filepath" - - "github.com/operator-framework/operator-sdk/internal/scaffold/input" - - log "github.com/sirupsen/logrus" -) - -const ( - // test constants describing an app operator project - appProjectName = "app-operator" - appRepo = "github.com/example-inc/" + appProjectName - appAPIVersion = "app.example.com/v1alpha1" - appKind = "AppService" -) - -var ( - appConfig = &input.Config{ - Repo: appRepo, - AbsProjectPath: mustGetImportPath(), - ProjectName: appProjectName, - } -) - -func mustGetImportPath() string { - wd, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get working directory: (%v)", err) - } - return filepath.Join(wd, filepath.FromSlash(appRepo)) -} - -func setupScaffoldAndWriter() (*Scaffold, *bytes.Buffer) { - buf := &bytes.Buffer{} - return &Scaffold{ - GetWriter: func(_ string, _ os.FileMode) (io.Writer, error) { - return buf, nil - }, - }, buf -} diff --git a/internal/util/fileutil/file_util.go b/internal/util/fileutil/file_util.go deleted file mode 100644 index fdc1f8d9c1..0000000000 --- a/internal/util/fileutil/file_util.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Modified from github.com/kubernetes-sigs/controller-tools/pkg/util/util.go - -package fileutil - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - "sync" - - log "github.com/sirupsen/logrus" - "github.com/spf13/afero" -) - -const ( - // file modes - DefaultDirFileMode = 0750 - DefaultFileMode = 0644 - DefaultExecFileMode = 0755 - - DefaultFileFlags = os.O_WRONLY | os.O_CREATE -) - -// FileWriter is a io wrapper to write files -type FileWriter struct { - fs afero.Fs - once sync.Once -} - -func NewFileWriterFS(fs afero.Fs) *FileWriter { - fw := &FileWriter{} - fw.once.Do(func() { - fw.fs = fs - }) - return fw -} - -func (fw *FileWriter) GetFS() afero.Fs { - fw.once.Do(func() { - fw.fs = afero.NewOsFs() - }) - return fw.fs -} - -// WriteCloser returns a WriteCloser to write to given path -func (fw *FileWriter) WriteCloser(path string, mode os.FileMode) (io.Writer, error) { - dir := filepath.Dir(path) - err := fw.GetFS().MkdirAll(dir, DefaultDirFileMode) - if err != nil { - return nil, err - } - - fi, err := fw.GetFS().OpenFile(path, DefaultFileFlags, mode) - if err != nil { - return nil, err - } - - return fi, nil -} - -// WriteFile write given content to the file path -func (fw *FileWriter) WriteFile(filePath string, content []byte) error { - f, err := fw.WriteCloser(filePath, DefaultFileMode) - if err != nil { - return fmt.Errorf("failed to create %s: %v", filePath, err) - } - - if c, ok := f.(io.Closer); ok { - defer func() { - if err := c.Close(); err != nil { - log.Fatal(err) - } - }() - } - - _, err = f.Write(content) - if err != nil { - return fmt.Errorf("failed to write %s: %v", filePath, err) - } - - return nil -} - -func IsClosedError(e error) bool { - pathErr, ok := e.(*os.PathError) - if !ok { - return false - } - if pathErr.Err == os.ErrClosed { - return true - } - return false -} - -// DotPath appends a current working directory dot, ex. "./" in Unix, -// to paths that do not already have one or are not absolute. -func DotPath(path string) string { - dotPrefix := "." + string(filepath.Separator) - if strings.HasPrefix(path, dotPrefix) || filepath.IsAbs(path) { - return path - } - return dotPrefix + path -} diff --git a/internal/util/k8sutil/api.go b/internal/util/k8sutil/api.go index f20b5a3192..47f181f5ef 100644 --- a/internal/util/k8sutil/api.go +++ b/internal/util/k8sutil/api.go @@ -18,9 +18,7 @@ import ( "bytes" "fmt" "io/ioutil" - "path" "path/filepath" - "regexp" "github.com/operator-framework/operator-registry/pkg/registry" log "github.com/sirupsen/logrus" @@ -175,84 +173,6 @@ func GVKsForV1beta1CustomResourceDefinitions(crds ...apiextv1beta1.CustomResourc return gvks } -// ParseGroupSubpackages parses the apisDir directory tree and returns a map of -// all found API groups to subpackages. -func ParseGroupSubpackages(apisDir string) (map[string][]string, error) { - return parseGroupSubdirs(apisDir, false) -} - -// ParseGroupVersions parses the apisDir directory tree and returns a map of -// all found API groups to versions. -func ParseGroupVersions(apisDir string) (map[string][]string, error) { - return parseGroupSubdirs(apisDir, true) -} - -// versionRegexp defines a kube-like version: -// https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning -var versionRegexp = regexp.MustCompile("^v[1-9][0-9]*((alpha|beta)[1-9][0-9]*)?$") - -// parseGroupSubdirs searches apisDir for all groups and potential version -// subdirs directly beneath each group dir, and returns a map of each group -// dir name to all children version dir names. If strictVersionMatch is true, -// all potential version dir names must strictly match versionRegexp. If -// false, all subdir names are considered valid. -func parseGroupSubdirs(apisDir string, strictVersionMatch bool) (map[string][]string, error) { - gvs := make(map[string][]string) - groups, err := ioutil.ReadDir(apisDir) - if err != nil { - return nil, fmt.Errorf("error reading directory %q to find API groups: %v", apisDir, err) - } - - for _, g := range groups { - if g.IsDir() { - groupDir := filepath.Join(apisDir, g.Name()) - versions, err := ioutil.ReadDir(groupDir) - if err != nil { - return nil, fmt.Errorf("error reading directory %q to find API versions: %v", groupDir, err) - } - - gvs[g.Name()] = make([]string, 0) - for _, v := range versions { - if v.IsDir() { - // Ignore directories that do not contain any files, so generators - // do not get empty directories as arguments. - verDir := filepath.Join(groupDir, v.Name()) - files, err := ioutil.ReadDir(verDir) - if err != nil { - return nil, fmt.Errorf("error reading directory %q to find API source files: %v", verDir, err) - } - for _, f := range files { - if !f.IsDir() && filepath.Ext(f.Name()) == ".go" { - // If strictVersionMatch is true, strictly check if v.Name() - // is a Kubernetes API version. - if !strictVersionMatch || versionRegexp.MatchString(v.Name()) { - gvs[g.Name()] = append(gvs[g.Name()], v.Name()) - } - break - } - } - } - } - } - } - - if len(gvs) == 0 { - return nil, fmt.Errorf("no groups or versions found in %s", apisDir) - } - return gvs, nil -} - -// 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 _, v := range vs { - apis = append(apis, path.Join(pkg, g, v)) - } - } - return apis -} - type CRDVersions []apiextv1beta1.CustomResourceDefinitionVersion func (vs CRDVersions) Len() int { return len(vs) } diff --git a/internal/util/k8sutil/constants.go b/internal/util/k8sutil/constants.go index dc03056b40..87532c2951 100644 --- a/internal/util/k8sutil/constants.go +++ b/internal/util/k8sutil/constants.go @@ -23,12 +23,4 @@ const ( // which is the namespace where the watch activity happens. // this value is empty if the operator is running with clusterScope. WatchNamespaceEnvVar = "WATCH_NAMESPACE" - - // OperatorNameEnvVar is the constant for env variable OPERATOR_NAME - // which is the name of the current operator - OperatorNameEnvVar = "OPERATOR_NAME" - - // PodNameEnvVar is the constant for env variable POD_NAME - // which is the name of the current pod. - PodNameEnvVar = "POD_NAME" ) diff --git a/internal/util/k8sutil/manifest.go b/internal/util/k8sutil/manifest.go deleted file mode 100644 index 6bb364d467..0000000000 --- a/internal/util/k8sutil/manifest.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package k8sutil - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/operator-framework/operator-sdk/internal/scaffold" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" - - log "github.com/sirupsen/logrus" - "sigs.k8s.io/yaml" -) - -var yamlSep = []byte("\n\n---\n\n") - -// CombineManifests combines given manifests with a base manifest and adds yaml -// style separation. Nothing is appended if the manifest is empty or base -// already contains a trailing separator. -func CombineManifests(base []byte, manifests ...[]byte) []byte { - // Base already has manifests we're appending to. - base = bytes.Trim(base, " \n") - if len(base) > 0 && len(manifests) > 0 { - if i := bytes.LastIndex(base, bytes.Trim(yamlSep, "\n")); i != len(base)-3 { - base = append(base, yamlSep...) - } - } - for j, manifest := range manifests { - if len(manifest) > 0 { - base = append(base, bytes.Trim(manifest, " \n")...) - // Don't append sep if manifest is the last element in manifests. - if j < len(manifests)-1 { - base = append(base, yamlSep...) - } - } - } - return append(base, '\n') -} - -// GenerateCombinedNamespacedManifest creates a temporary manifest yaml -// by combining all standard namespaced resource manifests in deployDir. -func GenerateCombinedNamespacedManifest(deployDir string) (*os.File, error) { - file, err := ioutil.TempFile("", "namespaced-manifest.yaml") - if err != nil { - return nil, err - } - defer func() { - if err := file.Close(); err != nil && !fileutil.IsClosedError(err) { - log.Errorf("Failed to close file %s: (%v)", file.Name(), err) - } - }() - - sa, err := ioutil.ReadFile(filepath.Join(deployDir, scaffold.ServiceAccountYamlFile)) - if err != nil { - log.Warnf("Could not find the serviceaccount manifest: (%v)", err) - } - role, err := ioutil.ReadFile(filepath.Join(deployDir, scaffold.RoleYamlFile)) - if err != nil { - log.Warnf("Could not find role manifest: (%v)", err) - } - roleBinding, err := ioutil.ReadFile(filepath.Join(deployDir, scaffold.RoleBindingYamlFile)) - if err != nil { - log.Warnf("Could not find role_binding manifest: (%v)", err) - } - operator, err := ioutil.ReadFile(filepath.Join(deployDir, scaffold.OperatorYamlFile)) - if err != nil { - return nil, fmt.Errorf("could not find operator manifest: %v", err) - } - combined := []byte{} - combined = CombineManifests(combined, sa, role, roleBinding, operator) - - if err := file.Chmod(os.FileMode(fileutil.DefaultFileMode)); err != nil { - return nil, fmt.Errorf("could not chown temporary namespaced manifest file: %v", err) - } - if _, err := file.Write(combined); err != nil { - return nil, fmt.Errorf("could not create temporary namespaced manifest file: %v", err) - } - if err := file.Close(); err != nil { - return nil, err - } - return file, nil -} - -// GenerateCombinedGlobalManifest creates a temporary manifest yaml -// by combining all standard global resource manifests in crdsDir. -func GenerateCombinedGlobalManifest(crdsDir string) (*os.File, error) { - file, err := ioutil.TempFile("", "global-manifest.yaml") - if err != nil { - return nil, err - } - defer func() { - if err := file.Close(); err != nil && !fileutil.IsClosedError(err) { - log.Errorf("Failed to close file %s: (%v)", file.Name(), err) - } - }() - - v1crds, v1beta1crds, err := GetCustomResourceDefinitions(crdsDir) - if err != nil { - return nil, fmt.Errorf("error getting CRD's from %s: %v", crdsDir, err) - } - combined := []byte{} - for _, crd := range v1crds { - b, err := yaml.Marshal(crd) - if err != nil { - return nil, fmt.Errorf("error marshaling %s: %v", crd.GetObjectKind().GroupVersionKind(), err) - } - combined = CombineManifests(combined, b) - } - for _, crd := range v1beta1crds { - b, err := yaml.Marshal(crd) - if err != nil { - return nil, fmt.Errorf("error marshaling %s: %v", crd.GetObjectKind().GroupVersionKind(), err) - } - combined = CombineManifests(combined, b) - } - - if err := file.Chmod(os.FileMode(fileutil.DefaultFileMode)); err != nil { - return nil, fmt.Errorf("could not chown temporary global manifest file: %v", err) - } - if _, err := file.Write(combined); err != nil { - return nil, fmt.Errorf("could not create temporary global manifest file: %v", err) - } - if err := file.Close(); err != nil { - return nil, err - } - return file, nil -} diff --git a/internal/util/kubebuilder/constants.go b/internal/util/kubebuilder/constants.go deleted file mode 100644 index 3347144736..0000000000 --- a/internal/util/kubebuilder/constants.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kbutil - -const ( - BinBuildDir = "bin" -) diff --git a/internal/util/kubebuilder/project.go b/internal/util/kubebuilder/project.go deleted file mode 100644 index 305d910544..0000000000 --- a/internal/util/kubebuilder/project.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 The Operator-SDK Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kbutil - -import ( - "io/ioutil" - "os" - - log "github.com/sirupsen/logrus" - "sigs.k8s.io/kubebuilder/pkg/model/config" -) - -const configFile = "PROJECT" - -// HasProjectFile returns true if the project is configured as a kubebuilder -// project. -func HasProjectFile() bool { - _, err := os.Stat(configFile) - if err != nil { - if os.IsNotExist(err) { - return false - } - log.Fatalf("Failed to read PROJECT file to detect kubebuilder project: %v", err) - } - return true -} - -// ReadConfig returns a configuration if a file containing one exists at the -// default path (project root). -func ReadConfig() (*config.Config, error) { - b, err := ioutil.ReadFile(configFile) - if err != nil { - return nil, err - } - c := &config.Config{} - if err = c.Unmarshal(b); err != nil { - return nil, err - } - return c, nil -} diff --git a/internal/util/projutil/exec.go b/internal/util/projutil/exec.go index 7853da768c..096f9f9be7 100644 --- a/internal/util/projutil/exec.go +++ b/internal/util/projutil/exec.go @@ -18,8 +18,6 @@ import ( "fmt" "os" "os/exec" - "path/filepath" - "strings" log "github.com/sirupsen/logrus" ) @@ -35,127 +33,6 @@ func ExecCmd(cmd *exec.Cmd) error { return nil } -// GoCmdOptions is the base option set for "go" subcommands. -type GoCmdOptions struct { - // BinName is the name of the compiled binary, passed to -o. - BinName string - // Args are args passed to "go {cmd}", aside from "-o {bin_name}" and - // test binary args. - // These apply to build, clean, get, install, list, run, and test. - Args []string - // PackagePath is the path to the main (go build) or test (go test) packages. - PackagePath string - // Env is a list of environment variables to pass to the cmd; - // exec.Command.Env is set to this value. - Env []string - // Dir is the dir to run "go {cmd}" in; exec.Command.Dir is set to this value. - Dir string -} - -// GoTestOptions is the set of options for "go test". -type GoTestOptions struct { - GoCmdOptions - // TestBinaryArgs are args passed to the binary compiled by "go test". - TestBinaryArgs []string -} - -var validVendorCmds = map[string]struct{}{ - "build": struct{}{}, - "clean": struct{}{}, - "get": struct{}{}, - "install": struct{}{}, - "list": struct{}{}, - "run": struct{}{}, - "test": struct{}{}, -} - -// GoBuild runs "go build" configured with opts. -func GoBuild(opts GoCmdOptions) error { - return GoCmd("build", opts) -} - -// GoTest runs "go test" configured with opts. -func GoTest(opts GoTestOptions) error { - bargs, err := opts.getGeneralArgsWithCmd("test") - if err != nil { - return err - } - bargs = append(bargs, opts.TestBinaryArgs...) - c := exec.Command("go", bargs...) - opts.setCmdFields(c) - return ExecCmd(c) -} - -// 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...) - opts.setCmdFields(c) - return ExecCmd(c) -} - -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 len(bargs) == 0 { - return nil, fmt.Errorf("the go binary cannot be run without subcommands") - } - - if opts.BinName != "" { - bargs = append(bargs, "-o", opts.BinName) - } - bargs = append(bargs, opts.Args...) - - if goModOn, err := GoModOn(); err != nil { - return nil, err - } else if goModOn { - // Does vendor exist? - info, err := os.Stat("vendor") - if err != nil && !os.IsNotExist(err) { - return nil, err - } - // Does the first "go" subcommand accept -mod=vendor? - _, ok := validVendorCmds[bargs[0]] - // TODO: remove needsModVendor when - // https://github.com/golang/go/issues/32471 is resolved. - if err == nil && info.IsDir() && ok && needsModVendor() { - bargs = append(bargs, "-mod=vendor") - } - } - - if opts.PackagePath != "" { - bargs = append(bargs, opts.PackagePath) - } - return bargs, nil -} - -// needsModVendor resolves https://github.com/golang/go/issues/32471, -// where any flags in GOFLAGS that are also set in the CLI are -// duplicated, causing 'go' invocation errors. -// TODO: remove once the issue is resolved. -func needsModVendor() bool { - return !strings.Contains(os.Getenv("GOFLAGS"), "-mod=vendor") -} - -func (opts GoCmdOptions) setCmdFields(c *exec.Cmd) { - c.Env = append(c.Env, os.Environ()...) - if len(opts.Env) != 0 { - c.Env = append(c.Env, opts.Env...) - } - if opts.Dir != "" { - c.Dir = opts.Dir - } -} - // From https://github.com/golang/go/wiki/Modules: // You can activate module support in one of two ways: // - Invoke the go command in a directory with a valid go.mod file in the @@ -178,19 +55,3 @@ func GoModOn() (bool, error) { return false, fmt.Errorf("unknown environment setting GO111MODULE=%s", v) } } - -func WdInGoPathSrc() (bool, error) { - wd, err := os.Getwd() - if err != nil { - return false, err - } - goPath, ok := os.LookupEnv(GoPathEnv) - if !ok || goPath == "" { - hd, err := getHomeDir() - if err != nil { - return false, err - } - goPath = filepath.Join(hd, "go") - } - return strings.HasPrefix(wd, filepath.Join(goPath, "src")), nil -} diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index 839a1fb901..39a7220c7f 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -18,31 +18,24 @@ 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" + "sigs.k8s.io/kubebuilder/pkg/model/config" +) - kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" +const ( + // Useful file modes. + DirMode = 0755 + FileMode = 0644 + ExecFileMode = 0755 ) const ( - GoPathEnv = "GOPATH" + // Go env vars. GoFlagsEnv = "GOFLAGS" GoModEnv = "GO111MODULE" - SrcDir = "src" - - fsep = string(filepath.Separator) - mainFile = "main.go" - managerMainFile = "cmd" + fsep + "manager" + fsep + mainFile - buildDockerfile = "build" + fsep + "Dockerfile" - goModFile = "go.mod" - defaultPermission = 0644 - - noticeColor = "\033[1;36m%s\033[0m" ) // OperatorType - the type of operator @@ -70,135 +63,38 @@ func (e ErrUnknownOperatorType) Error() string { return fmt.Sprintf(`unknown operator type "%v"`, e.Type) } -// MustInProjectRoot checks if the current dir is the project root, and exits -// if not. -func MustInProjectRoot() { - if err := CheckProjectRoot(); err != nil { - log.Fatal(err) - } -} - -// CheckProjectRoot checks if the current dir is the project root, and returns -// an error if not. -// "build/Dockerfile" may not be present in all projects -// todo: scaffold Project file for Ansible and Helm with the type information -func CheckProjectRoot() error { - if kbutil.HasProjectFile() { - return nil - } - - // todo(camilamacedo86): remove the following check when we no longer support the legacy scaffold layout - // 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 { +// HasProjectFile returns true if the project is configured as a kubebuilder +// project. +func HasProjectFile() bool { + _, err := os.Stat(configFile) + if err != nil { if os.IsNotExist(err) { - return fmt.Errorf("must run command in project root dir: project structure requires %s", - buildDockerfile) + return false } - return fmt.Errorf("error while checking if current directory is the project root: %v", err) + log.Fatalf("Failed to read PROJECT file to detect kubebuilder project: %v", err) } - return nil + return true } -// TODO: remove this (should use os.Getwd() or Config.ProjectName). -func MustGetwd() string { - wd, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get working directory: (%v)", err) - } - return wd -} +// Default config file path. +const configFile = "PROJECT" -func getHomeDir() (string, error) { - hd, err := homedir.Dir() +// ReadConfig returns a configuration if a file containing one exists at the +// default path (project root). +func ReadConfig() (*config.Config, error) { + b, err := ioutil.ReadFile(configFile) if err != nil { - return "", err - } - return homedir.Expand(hd) -} - -// TODO(hasbro17): If this function is called in the subdir of -// a module project it will fail to parse go.mod and return -// the correct import path. -// This needs to be fixed to return the pkg import path for any subdir -// in order for `generate csv` to correctly form pkg imports -// for API pkg paths that are not relative to the root dir. -// This might not be fixable since there is no good way to -// get the project root from inside the subdir of a module project. -// -// 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. -// If the project has a go.mod then wd must be the project root. -// -// 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 && !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) - } - 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 { - // MustSetWdGopath is necessary here because the user has set GOPATH, - // which could be a path list. - 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") + return nil, err } - return parseGoPkg(goPath) -} - -func parseGoPkg(gopath string) string { - goSrc := filepath.Join(gopath, SrcDir) - wd := MustGetwd() - pathedPkg := strings.Replace(wd, goSrc, "", 1) - // Make sure package only contains the "/" separator and no others, and - // trim any leading/trailing "/". - return strings.Trim(filepath.ToSlash(pathedPkg), "/") -} - -// GetOperatorType returns type of operator is in cwd. -// This function should be called after verifying the user is in project root. -func GetOperatorType() OperatorType { - switch { - // TODO: Differentiate between legacy and KB Go projects - case IsOperatorGo(): - return OperatorTypeGo - case IsOperatorAnsible(): - return OperatorTypeAnsible - case IsOperatorHelm(): - return OperatorTypeHelm + c := &config.Config{} + if err = c.Unmarshal(b); err != nil { + return nil, err } - return OperatorTypeUnknown + return c, nil } -// PluginKeyToOperatorType converts a plugin key string to an operator project -// type. -// TODO(estroz): this can probably be made more robust by checking known -// plugin keys directly. +// PluginKeyToOperatorType converts a plugin key string to an operator project type. +// TODO(estroz): this can probably be made more robust by checking known plugin keys directly. func PluginKeyToOperatorType(pluginKey string) OperatorType { switch { case strings.HasPrefix(pluginKey, "go"): @@ -211,78 +107,6 @@ func PluginKeyToOperatorType(pluginKey string) OperatorType { return OperatorTypeUnknown } -// IsOperatorGo returns true when the layout field in PROJECT file has the Go prefix key. -// NOTE: For the legacy, returns true when the project contains the cmd/manager directory and main.go file. -func IsOperatorGo() bool { - // If the project has the new layout we will check the type in the config file - if kbutil.HasProjectFile() { - cfg, err := kbutil.ReadConfig() - if err != nil { - log.Fatalf("Error reading config: %v", err) - } - return cfg.IsV2() || PluginKeyToOperatorType(cfg.Layout) == OperatorTypeGo - } - - // todo: remove the following code when the legacy layout is no longer supported - // we can check it using the Project File - _, err := os.Stat(managerMainFile) - if err == nil || os.IsExist(err) { - return true - } - // Aware of an alternative location for main.go. - _, err = os.Stat(mainFile) - return err == nil || os.IsExist(err) -} - -// IsOperatorAnsible returns true when the layout field in PROJECT file has the Ansible prefix key. -func IsOperatorAnsible() bool { - if !kbutil.HasProjectFile() { - return false - } - cfg, err := kbutil.ReadConfig() - if err != nil { - log.Fatalf("Error reading config: %v", err) - } - return PluginKeyToOperatorType(cfg.Layout) == OperatorTypeAnsible - -} - -// IsOperatorHelm returns true when the layout field in PROJECT file has the Helm prefix key. -func IsOperatorHelm() bool { - if !kbutil.HasProjectFile() { - return false - } - cfg, err := kbutil.ReadConfig() - if err != nil { - log.Fatalf("Error reading config: %v", err) - } - return PluginKeyToOperatorType(cfg.Layout) == OperatorTypeHelm -} - -// 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 - wd = MustGetwd() - ) - for _, newGopath = range filepath.SplitList(currentGopath) { - if strings.HasPrefix(filepath.Dir(wd), newGopath) { - cwdInGopath = true - break - } - } - if !cwdInGopath { - log.Fatalf("Project not in $GOPATH") - } - if err := os.Setenv(GoPathEnv, newGopath); err != nil { - log.Fatal(err) - } - return newGopath -} - var flagRe = regexp.MustCompile("(.* )?-v(.* )?") // SetGoVerbose sets GOFLAGS="${GOFLAGS} -v" if GOFLAGS does not @@ -298,24 +122,6 @@ func SetGoVerbose() error { return nil } -// CheckGoModules ensures that go modules are enabled. -func CheckGoModules() error { - goModOn, err := GoModOn() - if err != nil { - return err - } - if !goModOn { - return fmt.Errorf(`using go modules requires GO111MODULE="on", "auto", or unset.` + - ` More info: https://sdk.operatorframework.io/docs/golang/quickstart/#a-note-on-dependency-management`) - } - return nil -} - -// PrintDeprecationWarning prints a colored warning wrapping msg to the terminal. -func PrintDeprecationWarning(msg string) { - fmt.Fprintf(os.Stderr, noticeColor, "[Deprecation Notice] "+msg+"\n") -} - // RewriteFileContents adds newContent to the line after the last occurrence of target in filename's contents, // then writes the updated contents back to disk. func RewriteFileContents(filename, target, newContent string) error { @@ -329,7 +135,7 @@ func RewriteFileContents(filename, target, newContent string) error { return err } - err = ioutil.WriteFile(filename, []byte(modifiedContent), defaultPermission) + err = ioutil.WriteFile(filename, []byte(modifiedContent), FileMode) if err != nil { return fmt.Errorf("error writing modified contents to file, %v", err) } @@ -349,5 +155,4 @@ func appendContent(fileContents, target, newContent string) (string, error) { index := labelIndex + separationIndex + 1 return fileContents[:index] + newContent + fileContents[index:], nil - } diff --git a/pkg/ansible/proxy/kubeconfig/kubeconfig.go b/pkg/ansible/proxy/kubeconfig/kubeconfig.go index fd3bc5ee97..9328f1a49e 100644 --- a/pkg/ansible/proxy/kubeconfig/kubeconfig.go +++ b/pkg/ansible/proxy/kubeconfig/kubeconfig.go @@ -18,13 +18,12 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "html/template" "io/ioutil" "net/url" "os" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logf "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -103,7 +102,7 @@ func Create(ownerRef metav1.OwnerReference, proxyURL string, namespace string) ( // but we don't want to lose the error because we are // writing to the file, so we will call close twice. defer func() { - if err := file.Close(); err != nil && !fileutil.IsClosedError(err) { + if err := file.Close(); err != nil && !errors.Is(err, os.ErrClosed) { log.Error(err, "Failed to close generated kubeconfig file") } }() diff --git a/pkg/ansible/proxy/proxy_test.go b/pkg/ansible/proxy/proxy_test.go index 7bb1d0ea4c..85398d3b15 100644 --- a/pkg/ansible/proxy/proxy_test.go +++ b/pkg/ansible/proxy/proxy_test.go @@ -17,11 +17,12 @@ package proxy import ( "context" "encoding/json" + "errors" "io/ioutil" "net/http" + "os" "testing" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap" kcorev1 "k8s.io/api/core/v1" @@ -70,7 +71,7 @@ func TestHandler(t *testing.T) { t.Fatalf("Error getting pod from proxy: %v", err) } defer func() { - if err := resp.Body.Close(); err != nil && !fileutil.IsClosedError(err) { + if err := resp.Body.Close(); err != nil && !errors.Is(err, os.ErrClosed) { t.Errorf("Failed to close response body: (%v)", err) } }() diff --git a/pkg/ansible/runner/eventapi/eventapi.go b/pkg/ansible/runner/eventapi/eventapi.go index a6d19f06b3..b4d1e99e09 100644 --- a/pkg/ansible/runner/eventapi/eventapi.go +++ b/pkg/ansible/runner/eventapi/eventapi.go @@ -16,17 +16,17 @@ package eventapi import ( "encoding/json" + "errors" "fmt" "io" "io/ioutil" "net" "net/http" + "os" "strings" "sync" "time" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" - "github.com/go-logr/logr" logf "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -97,7 +97,7 @@ func (e *EventReceiver) Close() { e.stopped = true e.mutex.Unlock() e.logger.V(1).Info("Event API stopped") - if err := e.server.Close(); err != nil && !fileutil.IsClosedError(err) { + if err := e.server.Close(); err != nil && !errors.Is(err, os.ErrClosed) { e.logger.Error(err, "Failed to close event receiver") } close(e.Events) diff --git a/pkg/ansible/runner/internal/inputdir/inputdir.go b/pkg/ansible/runner/internal/inputdir/inputdir.go index d185a25621..3bc4c96c6a 100644 --- a/pkg/ansible/runner/internal/inputdir/inputdir.go +++ b/pkg/ansible/runner/internal/inputdir/inputdir.go @@ -16,15 +16,14 @@ package inputdir import ( "encoding/json" + "errors" "fmt" "io/ioutil" "os" "path/filepath" "strings" - "github.com/operator-framework/operator-sdk/internal/util/fileutil" "github.com/spf13/afero" - logf "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -176,7 +175,7 @@ func (i *InputDir) Write() error { return err } defer func() { - if err := f.Close(); err != nil && !fileutil.IsClosedError(err) { + if err := f.Close(); err != nil && !errors.Is(err, os.ErrClosed) { log.Error(err, "Failed to close playbook file") } }() diff --git a/pkg/ansible/watches/watches.go b/pkg/ansible/watches/watches.go index 1da5003ee8..f8db70f287 100644 --- a/pkg/ansible/watches/watches.go +++ b/pkg/ansible/watches/watches.go @@ -32,7 +32,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" yaml "sigs.k8s.io/yaml" - "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/pkg/ansible/flags" ) @@ -188,20 +187,25 @@ func (w *Watch) setValuesFromAlias(tmp alias) error { w.Finalizer = tmp.Finalizer w.AnsibleVerbosity = getAnsibleVerbosity(gvk, ansibleVerbosityDefault) w.Blacklist = tmp.Blacklist - w.addRolePlaybookPaths() + + wd, err := os.Getwd() + if err != nil { + return err + } + w.addRolePlaybookPaths(wd) w.Selector = parseLabelSelector(tmp.Selector) return nil } // addRolePlaybookPaths will add the full path based on the current dir -func (w *Watch) addRolePlaybookPaths() { +func (w *Watch) addRolePlaybookPaths(rootDir string) { if len(w.Playbook) > 0 { - w.Playbook = getFullPath(w.Playbook) + w.Playbook = getFullPath(rootDir, w.Playbook) } if len(w.Role) > 0 { - possibleRolePaths := getPossibleRolePaths(w.Role) + possibleRolePaths := getPossibleRolePaths(rootDir, w.Role) for _, possiblePath := range possibleRolePaths { if _, err := os.Stat(possiblePath); err == nil { w.Role = possiblePath @@ -210,7 +214,7 @@ func (w *Watch) addRolePlaybookPaths() { } } if w.Finalizer != nil && len(w.Finalizer.Role) > 0 { - possibleRolePaths := getPossibleRolePaths(w.Finalizer.Role) + possibleRolePaths := getPossibleRolePaths(rootDir, w.Finalizer.Role) for _, possiblePath := range possibleRolePaths { if _, err := os.Stat(possiblePath); err == nil { w.Finalizer.Role = possiblePath @@ -219,20 +223,20 @@ func (w *Watch) addRolePlaybookPaths() { } } if w.Finalizer != nil && len(w.Finalizer.Playbook) > 0 { - w.Finalizer.Playbook = getFullPath(w.Finalizer.Playbook) + w.Finalizer.Playbook = getFullPath(rootDir, w.Finalizer.Playbook) } } // getFullPath returns an absolute path for the playbook -func getFullPath(path string) string { +func getFullPath(rootDir, path string) string { if len(path) > 0 && !filepath.IsAbs(path) { - return filepath.Join(projutil.MustGetwd(), path) + return filepath.Join(rootDir, path) } return path } // getPossibleRolePaths returns list of possible absolute paths derived from a user provided value. -func getPossibleRolePaths(path string) []string { +func getPossibleRolePaths(rootDir, path string) []string { possibleRolePaths := []string{} if filepath.IsAbs(path) || len(path) == 0 { return append(possibleRolePaths, path) @@ -265,7 +269,7 @@ func getPossibleRolePaths(path string) []string { } } // Roles can also live in the current working directory. - return append(possibleRolePaths, getFullPath(filepath.Join("roles", path))) + return append(possibleRolePaths, getFullPath(rootDir, filepath.Join("roles", path))) } // Validate - ensures that a Watch is valid diff --git a/pkg/ansible/watches/watches_test.go b/pkg/ansible/watches/watches_test.go index df3385e837..2987d48f04 100644 --- a/pkg/ansible/watches/watches_test.go +++ b/pkg/ansible/watches/watches_test.go @@ -25,8 +25,6 @@ import ( "testing" "time" - "github.com/operator-framework/operator-sdk/internal/util/projutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -475,18 +473,22 @@ func TestLoad(t *testing.T) { os.Setenv("ANSIBLE_VERBOSITY_ANSIBLEVERBOSITYENV_APP_EXAMPLE_COM", "4") defer os.Unsetenv("ANSIBLE_VERBOSITY_ANSIBLEVERBOSITYENV_APP_EXAMPLE_COM") + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Test Load with ANSIBLE_ROLES_PATH var if tc.shouldSetAnsibleRolePathEnvVar { - anisbleEnvVar := "path/invalid:/path/invalid/myroles:" + projutil.MustGetwd() + anisbleEnvVar := "path/invalid:/path/invalid/myroles:" + wd os.Setenv("ANSIBLE_ROLES_PATH", anisbleEnvVar) defer os.Unsetenv("ANSIBLE_ROLES_PATH") } if tc.shouldSetAnsibleCollectionPathEnvVar { - ansibleCollectionPathEnv := filepath.Join(projutil.MustGetwd(), "testdata") + ansibleCollectionPathEnv := filepath.Join(wd, "testdata") os.Setenv("ANSIBLE_COLLECTIONS_PATH", ansibleCollectionPathEnv) defer os.Unsetenv("ANSIBLE_COLLECTIONS_PATH") } @@ -767,9 +769,16 @@ func TestAnsibleVerbosity(t *testing.T) { // Test the func getPossibleRolePaths. func TestGetPossibleRolePaths(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } // Mock default Full Path based in the current directory - rolesPath := filepath.Join(projutil.MustGetwd(), "roles") - home, _ := os.UserHomeDir() + rolesPath := filepath.Join(wd, "roles") + home, err := os.UserHomeDir() + if err != nil { + t.Fatal(err) + } type args struct { path string @@ -847,7 +856,7 @@ func TestGetPossibleRolePaths(t *testing.T) { defer os.Unsetenv("ANSIBLE_COLLECTIONS_PATH") } - allPathsToCheck := getPossibleRolePaths(tt.args.path) + allPathsToCheck := getPossibleRolePaths(wd, tt.args.path) sort.Strings(tt.want) sort.Strings(allPathsToCheck) if !reflect.DeepEqual(allPathsToCheck, tt.want) { diff --git a/pkg/helm/controller/reconcile.go b/pkg/helm/controller/reconcile.go index 1ac7b055c2..2d3811b5af 100644 --- a/pkg/helm/controller/reconcile.go +++ b/pkg/helm/controller/reconcile.go @@ -34,7 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/operator-framework/operator-sdk/internal/util/diffutil" + "github.com/operator-framework/operator-sdk/pkg/helm/internal/diff" "github.com/operator-framework/operator-sdk/pkg/helm/internal/types" "github.com/operator-framework/operator-sdk/pkg/helm/release" ) @@ -121,7 +121,7 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. } else { log.Info("Uninstalled release") if log.V(0).Enabled() { - fmt.Println(diffutil.Diff(uninstalledRelease.Manifest, "")) + fmt.Println(diff.Generate(uninstalledRelease.Manifest, "")) } status.SetCondition(types.HelmAppCondition{ Type: types.ConditionDeployed, @@ -206,7 +206,7 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. log.Info("Installed release") if log.V(0).Enabled() { - fmt.Println(diffutil.Diff("", installedRelease.Manifest)) + fmt.Println(diff.Generate("", installedRelease.Manifest)) } log.V(1).Info("Config values", "values", installedRelease.Config) message := "" @@ -265,7 +265,7 @@ func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile. log.Info("Upgraded release", "force", force) if log.V(0).Enabled() { - fmt.Println(diffutil.Diff(previousRelease.Manifest, upgradedRelease.Manifest)) + fmt.Println(diff.Generate(previousRelease.Manifest, upgradedRelease.Manifest)) } log.V(1).Info("Config values", "values", upgradedRelease.Config) message := "" diff --git a/internal/util/diffutil/diff_util.go b/pkg/helm/internal/diff/diff.go similarity index 94% rename from internal/util/diffutil/diff_util.go rename to pkg/helm/internal/diff/diff.go index dac27646ac..6a833fdf38 100644 --- a/internal/util/diffutil/diff_util.go +++ b/pkg/helm/internal/diff/diff.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package diffutil +package diff import ( "bytes" @@ -22,7 +22,8 @@ import ( "github.com/sergi/go-diff/diffmatchpatch" ) -func Diff(a, b string) string { +// Generate generates a diff between a and b, in color. +func Generate(a, b string) string { dmp := diffmatchpatch.New() wSrc, wDst, warray := dmp.DiffLinesToRunes(a, b)