diff --git a/cmd/operator-sdk/run/cmd.go b/cmd/operator-sdk/run/cmd.go index 8c5726af40..0f331a4312 100644 --- a/cmd/operator-sdk/run/cmd.go +++ b/cmd/operator-sdk/run/cmd.go @@ -19,15 +19,16 @@ import ( "fmt" "path/filepath" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + olmcatalog "github.com/operator-framework/operator-sdk/internal/generate/olm-catalog" olmoperator "github.com/operator-framework/operator-sdk/internal/olm/operator" k8sinternal "github.com/operator-framework/operator-sdk/internal/util/k8sutil" + kbutil "github.com/operator-framework/operator-sdk/internal/util/kubebuilder" "github.com/operator-framework/operator-sdk/internal/util/projutil" aoflags "github.com/operator-framework/operator-sdk/pkg/ansible/flags" hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" ) type runCmd struct { @@ -92,24 +93,30 @@ https://sdk.operatorframework.io/docs/olm-integration/cli-overview log.Fatalf("Failed to run operator using OLM: %v", err) } case c.local: - //TODO: remove namespace flag before 1.0.0 - // set --watch-namespace flag if the --namespace flag is set - // (only if --watch-namespace flag is not set) - if cmd.Flags().Changed("namespace") { - log.Info("--namespace is deprecated; use --watch-namespace instead.") - if !cmd.Flags().Changed("watch-namespace") { - err := cmd.Flags().Set("watch-namespace", c.namespace) - return err + // The main.go and manager.yaml scaffolds in the new layout do not support the WATCH_NAMESPACE + // env var to configure the namespace that the operator watches. The default is all namespaces. + // So this flag is unsupported for the new layout. + if !kbutil.HasProjectFile() { + //TODO: remove namespace flag before 1.0.0 + // set --watch-namespace flag if the --namespace flag is set + // (only if --watch-namespace flag is not set) + if cmd.Flags().Changed("namespace") { // not valid for te new layout + log.Info("--namespace is deprecated; use --watch-namespace instead.") + if !cmd.Flags().Changed("watch-namespace") { + err := cmd.Flags().Set("watch-namespace", c.namespace) + return err + } } - } - // Get default namespace to watch if unset. - if !cmd.Flags().Changed("watch-namespace") { - _, defaultNamespace, err := k8sinternal.GetKubeconfigAndNamespace(c.kubeconfig) - if err != nil { - return fmt.Errorf("error getting kubeconfig and default namespace: %v", err) + // Get default namespace to watch if unset. + if !cmd.Flags().Changed("watch-namespace") { + _, defaultNamespace, err := k8sinternal.GetKubeconfigAndNamespace(c.kubeconfig) + if err != nil { + return fmt.Errorf("error getting kubeconfig and default namespace: %v", err) + } + c.localArgs.watchNamespace = defaultNamespace } - c.localArgs.watchNamespace = defaultNamespace } + c.localArgs.kubeconfig = c.kubeconfig if err := c.localArgs.run(); err != nil { log.Fatalf("Failed to run operator locally: %v", err) @@ -127,9 +134,11 @@ https://sdk.operatorframework.io/docs/olm-integration/cli-overview "specified by $KUBECONFIG, or to default file rules if not set") // Deprecated: namespace exists for historical compatibility. Use watch-namespace instead. //TODO: remove namespace flag before 1.0.0 - cmd.Flags().StringVar(&c.namespace, "namespace", "", - "(Deprecated: use --watch-namespace instead.)"+ - "The namespace where the operator watches for changes.") + if !kbutil.HasProjectFile() { // not show for the kb layout projects + cmd.Flags().StringVar(&c.namespace, "namespace", "", + "(Deprecated: use --watch-namespace instead.)"+ + "The namespace where the operator watches for changes.") + } // 'run --olm' and related flags. cmd.Flags().BoolVar(&c.olm, "olm", false, "The operator to be run will be managed by OLM in a cluster. "+ diff --git a/cmd/operator-sdk/run/local.go b/cmd/operator-sdk/run/local.go index 8bd9753630..22d53dcb37 100644 --- a/cmd/operator-sdk/run/local.go +++ b/cmd/operator-sdk/run/local.go @@ -25,7 +25,12 @@ import ( "strings" "syscall" + log "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "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/operator-framework/operator-sdk/pkg/ansible" aoflags "github.com/operator-framework/operator-sdk/pkg/ansible/flags" @@ -33,10 +38,6 @@ import ( hoflags "github.com/operator-framework/operator-sdk/pkg/helm/flags" "github.com/operator-framework/operator-sdk/pkg/k8sutil" "github.com/operator-framework/operator-sdk/pkg/log/zap" - - log "github.com/sirupsen/logrus" - "github.com/spf13/pflag" - logf "sigs.k8s.io/controller-runtime/pkg/log" ) type runLocalArgs struct { @@ -52,9 +53,15 @@ type runLocalArgs struct { func (c *runLocalArgs) addToFlags(fs *pflag.FlagSet) { prefix := "[local only] " - fs.StringVar(&c.watchNamespace, "watch-namespace", "", - prefix+"The namespace where the operator watches for changes. Set \"\" for AllNamespaces, "+ - "set \"ns1,ns2\" for MultiNamespace") + // The main.go and manager.yaml scaffolds in the new layout do not support the WATCH_NAMESPACE + // env var to configure the namespace that the operator watches. The default is all namespaces. + // So this flag is unsupported for the new layout. + if !kbutil.HasProjectFile() { + fs.StringVar(&c.watchNamespace, "watch-namespace", "", + prefix+"The namespace where the operator watches for changes. Set \"\" for AllNamespaces, "+ + "set \"ns1,ns2\" for MultiNamespace") + } + fs.StringVar(&c.operatorFlags, "operator-flags", "", prefix+"The flags that the operator needs. Example: \"--flag1 value1 --flag2=value2\"") fs.StringVar(&c.ldFlags, "go-ldflags", "", prefix+"Set Go linker options") @@ -63,8 +70,12 @@ func (c *runLocalArgs) addToFlags(fs *pflag.FlagSet) { } func (c runLocalArgs) run() error { - log.Infof("Running the operator locally in namespace %s.", c.watchNamespace) - + // The new layout will not have c.watchNamespace + if kbutil.HasProjectFile() { + log.Infof("Running the operator locally ...") + } else { + log.Infof("Running the operator locally; watching namespace %q", c.watchNamespace) + } switch t := projutil.GetOperatorType(); t { case projutil.OperatorTypeGo: return c.runGo() @@ -76,65 +87,39 @@ func (c runLocalArgs) run() error { return projutil.ErrUnknownOperatorType{} } +// runGo will run the project locally for the Go Type projects func (c runLocalArgs) runGo() error { - projutil.MustInProjectRoot() - absProjectPath := projutil.MustGetwd() - projectName := filepath.Base(absProjectPath) - outputBinName := filepath.Join(scaffold.BuildBinDir, projectName+"-local") - if runtime.GOOS == "windows" { - outputBinName += ".exe" - } - if err := c.buildLocal(outputBinName); err != nil { - return fmt.Errorf("failed to build operator to run locally: %v", err) - } - - args := []string{} - if c.operatorFlags != "" { - extraArgs := strings.Split(c.operatorFlags, " ") - args = append(args, extraArgs...) + // Build the project and generate binary that will be executed + binName, err := c.generateBinary() + if err != nil { + return err } - - var dc *exec.Cmd - + // Get the args that will be used to exec the binary. + // Users are allowed to use the flag operator-flags to pass any value that they may wish + args := c.argsFromOperatorFlags() + // Build the command + var cmd *exec.Cmd if c.enableDelve { - delveArgs := []string{"--listen=:2345", "--headless=true", "--api-version=2", "exec", outputBinName, "--"} - delveArgs = append(delveArgs, args...) - - dc = exec.Command("dlv", delveArgs...) - log.Infof("Delve debugger enabled with args %s", delveArgs) + cmd = getExecCmdWithDebugger(binName, args) } else { - dc = exec.Command(outputBinName, args...) + cmd = exec.Command(binName, args...) } - + // Kill the command if an exit signal is received. ch := make(chan os.Signal) signal.Notify(ch, os.Interrupt, syscall.SIGTERM) go func() { <-ch - err := dc.Process.Kill() + err := cmd.Process.Kill() if err != nil { log.Fatalf("Failed to terminate the operator: (%v)", err) } os.Exit(0) }() - dc.Env = os.Environ() - dc.Env = append(dc.Env, fmt.Sprintf("%s=%s", k8sutil.ForceRunModeEnv, k8sutil.LocalRunMode)) - // only set env var if user explicitly specified a kubeconfig path - if c.kubeconfig != "" { - dc.Env = append(dc.Env, fmt.Sprintf("%v=%v", k8sutil.KubeConfigEnvVar, c.kubeconfig)) - } - dc.Env = append(dc.Env, fmt.Sprintf("%v=%v", k8sutil.WatchNamespaceEnvVar, c.watchNamespace)) - - // Set the ANSIBLE_ROLES_PATH - if c.ansibleOperatorFlags != nil && len(c.ansibleOperatorFlags.AnsibleRolesPath) > 0 { - log.Info(fmt.Sprintf("set the value %v for environment variable %v.", c.ansibleOperatorFlags.AnsibleRolesPath, - aoflags.AnsibleRolesPathEnvVar)) - dc.Env = append(dc.Env, fmt.Sprintf("%v=%v", aoflags.AnsibleRolesPathEnvVar, c.ansibleOperatorFlags.AnsibleRolesPath)) - } - - if err := projutil.ExecCmd(dc); err != nil { + // Add default env vars and values informed via flags + c.addEnvVars(cmd) + if err := projutil.ExecCmd(cmd); err != nil { return fmt.Errorf("failed to run operator locally: %v", err) } - return nil } @@ -154,6 +139,7 @@ func (c runLocalArgs) runHelm() error { return helm.Run(c.helmOperatorFlags) } +// setupOperatorEnv will add envvar for the kubeconfig and namespace informed func setupOperatorEnv(kubeconfig, namespace string) error { // Set the kubeconfig that the manager will be able to grab // only set env var if user explicitly specified a kubeconfig path @@ -168,8 +154,6 @@ func setupOperatorEnv(kubeconfig, namespace string) error { return fmt.Errorf("failed to set %s environment variable: %v", k8sutil.WatchNamespaceEnvVar, err) } } - // Set the operator name, if not already set - projutil.MustInProjectRoot() if _, err := k8sutil.GetOperatorName(); err != nil { operatorName := filepath.Base(projutil.MustGetwd()) if err := os.Setenv(k8sutil.OperatorNameEnvVar, operatorName); err != nil { @@ -179,7 +163,8 @@ func setupOperatorEnv(kubeconfig, namespace string) error { return nil } -func (c runLocalArgs) buildLocal(outputBinName string) error { +// getBuildRunLocalArgs returns go build args for -ldflags and -gcflags +func (c runLocalArgs) getBuildRunLocalArgs() []string { var args []string if c.ldFlags != "" { args = []string{"-ldflags", c.ldFlags} @@ -187,10 +172,84 @@ func (c runLocalArgs) buildLocal(outputBinName string) error { if c.enableDelve { args = append(args, "-gcflags=\"all=-N -l\"") } + return args +} + +// generateBinary will build the Go project by running the command `go build -o bin/manager main.go` +func (c runLocalArgs) generateBinary() (string, error) { + // Define name of the bin and where is the main.go pkg for each layout + var outputBinName, packagePath string + if kbutil.HasProjectFile() { + outputBinName = filepath.Join(kbutil.BinBuildDir, getProjectName()+"-local") + packagePath = projutil.GetGoPkg() + } else { + // todo: remove the if, else when the legacy code is no longer supported + packagePath = path.Join(projutil.GetGoPkg(), filepath.ToSlash(scaffold.ManagerDir)) + outputBinName = filepath.Join(scaffold.BuildBinDir, getProjectName()+"-local") + } + // allow the command works in windows SO + if runtime.GOOS == "windows" { + outputBinName += ".exe" + } opts := projutil.GoCmdOptions{ BinName: outputBinName, - PackagePath: path.Join(projutil.GetGoPkg(), filepath.ToSlash(scaffold.ManagerDir)), - Args: args, + PackagePath: packagePath, + Args: c.getBuildRunLocalArgs(), + } + if err := projutil.GoBuild(opts); err != nil { + return "", err + } + return outputBinName, nil +} + +// execCmdForBinWithDebugger will exec the command with the delve required args +// Note that delve is a debugger for the Go programming language. +// More info: https://github.com/go-delve/delve +func getExecCmdWithDebugger(binName string, args []string) *exec.Cmd { + delveArgs := []string{"--listen=:2345", "--headless=true", "--api-version=2", "exec", binName, "--"} + delveArgs = append(delveArgs, args...) + log.Infof("Delve debugger enabled with args %s", delveArgs) + return exec.Command("dlv", delveArgs...) +} + +// addEnvVars will add the EnvVars to the command informed +func (c runLocalArgs) addEnvVars(cmd *exec.Cmd) { + cmd.Env = os.Environ() + + // Set EnvVar to let the project knows that it is running outside of the cluster + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k8sutil.ForceRunModeEnv, k8sutil.LocalRunMode)) + + // Only set env var if user explicitly specified a kubeconfig path + if c.kubeconfig != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("%v=%v", k8sutil.KubeConfigEnvVar, c.kubeconfig)) } - return projutil.GoBuild(opts) + + // Set WATCH_NAMESPACE with the value informed via flag + cmd.Env = append(cmd.Env, fmt.Sprintf("%v=%v", k8sutil.WatchNamespaceEnvVar, c.watchNamespace)) + + // todo: check if it should really be here. Shows that it should be part of AnsibleRun only. + // Set the ANSIBLE_ROLES_PATH + if c.ansibleOperatorFlags != nil && len(c.ansibleOperatorFlags.AnsibleRolesPath) > 0 { + log.Info(fmt.Sprintf("set the value %v for environment variable %v.", + c.ansibleOperatorFlags.AnsibleRolesPath, aoflags.AnsibleRolesPathEnvVar)) + cmd.Env = append(cmd.Env, fmt.Sprintf("%v=%v", aoflags.AnsibleRolesPathEnvVar, + c.ansibleOperatorFlags.AnsibleRolesPath)) + } +} + +// argsFromOperatorFlags will return an array with all args used in the flags +func (c runLocalArgs) argsFromOperatorFlags() []string { + args := []string{} + if c.operatorFlags != "" { + extraArgs := strings.Split(c.operatorFlags, " ") + args = append(args, extraArgs...) + } + return args +} + +// getProjectName will return the name of the project. This function only works if the current working directory +// is the project root. +func getProjectName() string { + absProjectPath := projutil.MustGetwd() + return filepath.Base(absProjectPath) } diff --git a/internal/util/kubebuilder/constants.go b/internal/util/kubebuilder/constants.go new file mode 100644 index 0000000000..3347144736 --- /dev/null +++ b/internal/util/kubebuilder/constants.go @@ -0,0 +1,19 @@ +// 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/projutil/project_util.go b/internal/util/projutil/project_util.go index 5200e4085d..5f8d6d5972 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -206,6 +206,14 @@ func GetOperatorType() OperatorType { } func IsOperatorGo() bool { + // todo: in the future we should check the plugin prefix to ensure the operator type + // for now, we can assume that any project with the kubebuilder layout is Go Type + if kbutil.HasProjectFile() { + return true + } + + // 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