kubectl: refactor addFromEnvFile, write tests

pull/6/head
Alexander Campbell 2017-05-25 14:23:00 -07:00
parent 7b54199fd5
commit 63e9c67db8
3 changed files with 157 additions and 27 deletions

View File

@ -14,6 +14,7 @@ go_test(
"cluster_test.go",
"configmap_test.go",
"deployment_test.go",
"env_file_test.go",
"generate_test.go",
"kubectl_test.go",
"namespace_test.go",

View File

@ -28,6 +28,47 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// proccessEnvFileLine returns a blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary.
func proccessEnvFileLine(line []byte, filePath string,
currentLine int) (key, value string, err error) {
if !utf8.Valid(line) {
return ``, ``, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v",
filePath, currentLine+1, line)
}
// We trim UTF8 BOM from the first line of the file but no others
if currentLine == 0 {
line = bytes.TrimPrefix(line, utf8bom)
}
// trim the line from all leading whitespace first
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// If the line is empty or a comment, we return a blank key/value pair.
if len(line) == 0 || line[0] == '#' {
return ``, ``, nil
}
data := strings.SplitN(string(line), "=", 2)
key = data[0]
if errs := validation.IsCIdentifier(key); len(errs) != 0 {
return ``, ``, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
if len(data) == 2 {
value = data[1]
} else {
// No value (no `=` in the line) is a signal to obtain the value
// from the environment.
value = os.Getenv(key)
}
return
}
// addFromEnvFile processes an env file allows a generic addTo to handle the
// collection of key value pairs or returns an error.
func addFromEnvFile(filePath string, addTo func(key, value string) error) error {
@ -39,38 +80,23 @@ func addFromEnvFile(filePath string, addTo func(key, value string) error) error
scanner := bufio.NewScanner(f)
currentLine := 0
utf8bom := []byte{0xEF, 0xBB, 0xBF}
for scanner.Scan() {
// Proccess the current line, retrieving a key/value pair if
// possible.
scannedBytes := scanner.Bytes()
if !utf8.Valid(scannedBytes) {
return fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filePath, currentLine+1, scannedBytes)
key, value, err := proccessEnvFileLine(scannedBytes, filePath, currentLine)
if err != nil {
return err
}
// We trim UTF8 BOM
if currentLine == 0 {
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
}
// trim the line from all leading whitespace first
line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
currentLine++
// line is not empty, and not starting with '#'
if len(line) > 0 && !strings.HasPrefix(line, "#") {
data := strings.SplitN(line, "=", 2)
key := data[0]
if errs := validation.IsCIdentifier(key); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
value := ""
if len(data) > 1 {
// pass the value through, no trimming
value = data[1]
} else {
// a pass-through variable is given
value = os.Getenv(key)
}
if err = addTo(key, value); err != nil {
return err
}
if len(key) == 0 {
// no key means line was empty or a comment
continue
}
if err = addTo(key, value); err != nil {
return err
}
}
return nil

View File

@ -0,0 +1,103 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
import (
"os"
"strings"
"testing"
)
// Test the cases of proccessEnvFileLine that can be run without touching the
// environment.
func Test_processEnvFileLine(t *testing.T) {
testCases := []struct {
name string
line []byte
currentLine int
expectedKey string
expectedValue string
expectedErr string
}{
{"the utf8bom is trimmed on the first line",
append(utf8bom, 'a', '=', 'c'), 0, "a", "c", ""},
{"the utf8bom is NOT trimmed on the second line",
append(utf8bom, 'a', '=', 'c'), 1, "", "", "not a valid key name"},
{"no key is returned on a comment line",
[]byte{' ', '#', 'c'}, 0, "", "", ""},
{"no key is returned on a blank line",
[]byte{' ', ' ', '\t'}, 0, "", "", ""},
{"key is returned even with no value",
[]byte{' ', 'x', '='}, 0, "x", "", ""},
}
for _, c := range testCases {
key, value, err := proccessEnvFileLine(c.line, `filename`, c.currentLine)
t.Logf("Testing that %s.", c.name)
if c.expectedKey != key {
t.Errorf("\texpected key %q, received %q", c.expectedKey, key)
}
if c.expectedValue != value {
t.Errorf("\texpected value %q, received %q", c.expectedValue, value)
}
if len(c.expectedErr) == 0 {
if err != nil {
t.Errorf("\tunexpected err %v", err)
}
} else {
if !strings.Contains(err.Error(), c.expectedErr) {
t.Errorf("\terr %v doesn't match expected %q", err, c.expectedErr)
}
}
}
}
// proccessEnvFileLine needs to fetch the value from the environment if no
// equals sign is provided.
// For example:
//
// my_key1=alpha
// my_key2=beta
// my_key3
//
// In this file, my_key3 must be fetched from the environment.
// Test this capability.
func Test_processEnvFileLine_readEnvironment(t *testing.T) {
const realKey = "k8s_test_env_file_key"
const realValue = `my_value`
// Just in case, these two lines ensure the environment is restored to
// its original state.
original := os.Getenv(realKey)
defer func() { os.Setenv(realKey, original) }()
os.Setenv(realKey, `my_value`)
key, value, err := proccessEnvFileLine([]byte(realKey), `filename`, 3)
if err != nil {
t.Fatal(err)
}
if key != realKey {
t.Errorf(`expected key %q, received %q`, realKey, key)
}
if value != realValue {
t.Errorf(`expected value %q, received %q`, realValue, value)
}
}