Skip to content

Commit

Permalink
Add option to read the operator namespace from an envirnment variable…
Browse files Browse the repository at this point in the history
…, or from a non-standard SA file.

While developing an operator, it is impossible to run from a local environment variable if the code
is trying to access the namespace file. For example, calling the `conditions.NewCondition` function
will return an error when the operator is running in a development environment.

This PR add to options to override the hard-coded path to the namespace file with another location:
1. by setting the new `OPERATOR_NAMESPACE` environment variable to the required namespace.
2. by setting the new `SA_FILE_PATH` environment variable to the local path of the namespace file.

If both new environment variables are set, the namespace will be taken from the `OPERATOR_NAMESPACE`
environment variable.

Fixes operator-framework#50

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>
  • Loading branch information
nunnatsa committed Jan 24, 2021
1 parent c0ba7dc commit 5f2eff7
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 6 deletions.
1 change: 1 addition & 0 deletions internal/utils/testfiles/namespace
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testnamespace
3 changes: 3 additions & 0 deletions internal/utils/testfiles/namespaceWithSpaces
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
testnamespace


26 changes: 25 additions & 1 deletion internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,41 @@ import (
"strings"
)

const (
// SAFileDefaultLocation default location of the service account namespace file
SAFileDefaultLocation = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"

// SAFileLocationEnv is the name of the environment variable that holds the service
// account file location file. It is not set by default, but setting it allows operator
// developers set different file location, because the default path may not be accessible
// on the development environment. If not set, the default path will be used.
SAFileLocationEnv = "SA_FILE_PATH"

// OperatorNamespaceEnv the name of the environm,ent variable that holds the namespace.
// If set, the GetOperatorNamespace method returns its value. If not, the method read the
// service account file.
OperatorNamespaceEnv = "OPERATOR_NAMESPACE"
)

// ErrNoNamespace indicates that a namespace could not be found for the current
// environment
var ErrNoNamespace = fmt.Errorf("namespace not found for current environment")

var readSAFile = func() ([]byte, error) {
return ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
saFileLocation, found := os.LookupEnv(SAFileLocationEnv)
if !found {
saFileLocation = SAFileDefaultLocation
}
return ioutil.ReadFile(saFileLocation)
}

// GetOperatorNamespace returns the namespace the operator should be running in from
// the associated service account secret.
var GetOperatorNamespace = func() (string, error) {
if ns := strings.TrimSpace(os.Getenv(OperatorNamespaceEnv)); ns != "" {
return ns, nil
}

nsBytes, err := readSAFile()
if err != nil {
if os.IsNotExist(err) {
Expand Down
121 changes: 116 additions & 5 deletions internal/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ package utils

import (
"os"
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("Helpers test", func() {
Describe("GetOperatorNamespace", func() {
var origReadSAFile = readSAFile
AfterEach(func() {
readSAFile = origReadSAFile
})
const testNamespace = "testnamespace"
It("should return error when namespace not found", func() {
readSAFile = func() ([]byte, error) {
return nil, os.ErrNotExist
Expand All @@ -33,24 +39,129 @@ var _ = Describe("Helpers test", func() {
})
It("should return namespace", func() {
readSAFile = func() ([]byte, error) {
return []byte("testnamespace"), nil
return []byte(testNamespace), nil
}

// test
namespace, err := GetOperatorNamespace()
Expect(err).Should(BeNil())
Expect(namespace).To(Equal("testnamespace"))
Expect(namespace).To(Equal(testNamespace))
})
It("should trim whitespace from namespace", func() {
readSAFile = func() ([]byte, error) {
return []byte(" testnamespace "), nil
return []byte(" " + testNamespace + " "), nil
}

// test
namespace, err := GetOperatorNamespace()
Expect(err).Should(BeNil())
Expect(namespace).To(Equal("testnamespace"))
Expect(err).ShouldNot(HaveOccurred())
Expect(namespace).To(Equal(testNamespace))
})
Context("read namespace from environment variable", func() {
var originalVal string
JustBeforeEach(func() {
originalVal = os.Getenv(OperatorNamespaceEnv)
})
JustAfterEach(func() {
err := os.Setenv(OperatorNamespaceEnv, originalVal)
Expect(err).ShouldNot(HaveOccurred())
})
It("should return the env var value, if set", func() {
err := os.Setenv(OperatorNamespaceEnv, testNamespace)
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).ShouldNot(HaveOccurred())
Expect(namespace).To(Equal(testNamespace))
})
It("should trim spaces from the namespace", func() {
err := os.Setenv(OperatorNamespaceEnv, " "+testNamespace+" ")
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).ShouldNot(HaveOccurred())
Expect(namespace).To(Equal(testNamespace))
})
It("should return the namespace from a file if not the env var is not set", func() {
readSAFile = func() ([]byte, error) {
return []byte("namespace-from-file"), nil
}
err := os.Unsetenv(OperatorNamespaceEnv)
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).Should(BeNil())
Expect(namespace).To(Equal("namespace-from-file"))

})
It("should return the namespace from a file if not the env var is only spaces", func() {
readSAFile = func() ([]byte, error) {
return []byte("namespace-from-file"), nil
}
err := os.Setenv(OperatorNamespaceEnv, " ")
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).Should(BeNil())
Expect(namespace).To(Equal("namespace-from-file"))
})
})
Context("read namespace from non standard location", func() {
var originalVal string
JustBeforeEach(func() {
originalVal = os.Getenv(SAFileLocationEnv)
})
JustAfterEach(func() {
err := os.Setenv(SAFileLocationEnv, originalVal)
Expect(err).ShouldNot(HaveOccurred())
})
It("should return the env var value, if set", func() {
err := os.Setenv(SAFileLocationEnv, getTestFilesDir()+"namespace")
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).ShouldNot(HaveOccurred())
Expect(namespace).To(Equal(testNamespace))
})
It("should trim spaces from the namespace", func() {
err := os.Setenv(SAFileLocationEnv, getTestFilesDir()+"namespaceWithSpaces")
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).ShouldNot(HaveOccurred())

Expect(namespace).To(Equal(testNamespace))
})
It("should return error if the file is not exists", func() {
err := os.Setenv(SAFileLocationEnv, getTestFilesDir()+"notExists")
Expect(err).ShouldNot(HaveOccurred())

namespace, err := GetOperatorNamespace()
Expect(err).Should(HaveOccurred())
Expect(err).Should(Equal(ErrNoNamespace))

Expect(namespace).Should(BeEmpty())
})
})
})

})

// return the path to the test files directory
func getTestFilesDir() string {
const (
packageUnderTestPath = "internal/utils"
testFileDir = "/testfiles/"
)

wd, err := os.Getwd()
ExpectWithOffset(1, err).ShouldNot(HaveOccurred())

// if running form internal/utils/
if strings.HasSuffix(wd, packageUnderTestPath) {
return wd + testFileDir
}

// if running from repository root
return packageUnderTestPath + testFileDir
}

0 comments on commit 5f2eff7

Please sign in to comment.