Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions cmd/operator-sdk/run/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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. "+
Expand Down
175 changes: 117 additions & 58 deletions cmd/operator-sdk/run/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ 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"
"github.com/operator-framework/operator-sdk/pkg/helm"
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"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just sort the imports and added the kbutils one.

)

type runLocalArgs struct {
Expand All @@ -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")
Comment on lines +60 to +62
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like more changes related to this are required around here and here

Copy link
Copy Markdown
Contributor Author

@camilamacedo86 camilamacedo86 May 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first contidional here It will set watch-namepace == namespace if the flag is set. Since the flag does not exist when it is in the new layout all is fine. Anyway, we can be redundant and add a check as well to ensure case something change.

However, for the second conditional here you are right we need set it for cluster-scoped.

}

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")
Expand All @@ -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()
Expand All @@ -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
}

Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -179,18 +163,93 @@ 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}
}
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)
}
19 changes: 19 additions & 0 deletions internal/util/kubebuilder/constants.go
Original file line number Diff line number Diff line change
@@ -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"
)
8 changes: 8 additions & 0 deletions internal/util/projutil/project_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down