mirror of https://github.com/k3s-io/k3s
create configmap from-env-file
parent
c3d7ee11d1
commit
7eb49628c6
|
@ -271,6 +271,7 @@ forward-services
|
|||
framework-name
|
||||
framework-store-uri
|
||||
framework-weburi
|
||||
from-env-file
|
||||
from-file
|
||||
from-literal
|
||||
func-dest
|
||||
|
|
|
@ -49,7 +49,13 @@ var (
|
|||
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
|
||||
|
||||
# Create a new configmap named my-config with key1=config1 and key2=config2
|
||||
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2`)
|
||||
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
|
||||
|
||||
# Create a new configmap named my-config from the key=value pairs in the file
|
||||
kubectl create configmap my-config --from-file=path/to/bar
|
||||
|
||||
# Create a new configmap named my-config from an env file
|
||||
kubectl create configmap my-config --from-env-file=path/to/bar.env`)
|
||||
)
|
||||
|
||||
// ConfigMap is a command to ease creating ConfigMaps.
|
||||
|
@ -71,6 +77,7 @@ func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
|
|||
cmdutil.AddGeneratorFlags(cmd, cmdutil.ConfigMapV1GeneratorName)
|
||||
cmd.Flags().StringSlice("from-file", []string{}, "Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
|
||||
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
||||
cmd.Flags().String("from-env-file", "", "Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -87,6 +94,7 @@ func CreateConfigMap(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
|
|||
Name: name,
|
||||
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
|
||||
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
|
||||
}
|
||||
default:
|
||||
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
|
||||
|
|
|
@ -38,6 +38,8 @@ type ConfigMapGeneratorV1 struct {
|
|||
FileSources []string
|
||||
// LiteralSources to derive the configMap from (optional)
|
||||
LiteralSources []string
|
||||
// EnvFileSource to derive the configMap from (optional)
|
||||
EnvFileSource string
|
||||
}
|
||||
|
||||
// Ensure it supports the generator pattern that uses parameter injection.
|
||||
|
@ -79,6 +81,15 @@ func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (ru
|
|||
}
|
||||
params[key] = strVal
|
||||
}
|
||||
fromEnvFileString, found := genericParams["from-env-file"]
|
||||
if found {
|
||||
fromEnvFile, isString := fromEnvFileString.(string)
|
||||
if !isString {
|
||||
return nil, fmt.Errorf("expected string, found :%v", fromEnvFileString)
|
||||
}
|
||||
delegate.EnvFileSource = fromEnvFile
|
||||
delete(genericParams, "from-env-file")
|
||||
}
|
||||
delegate.Name = params["name"]
|
||||
delegate.Type = params["type"]
|
||||
return delegate.StructuredGenerate()
|
||||
|
@ -91,6 +102,7 @@ func (s ConfigMapGeneratorV1) ParamNames() []GeneratorParam {
|
|||
{"type", false},
|
||||
{"from-file", false},
|
||||
{"from-literal", false},
|
||||
{"from-env-file", false},
|
||||
{"force", false},
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +125,11 @@ func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(s.EnvFileSource) > 0 {
|
||||
if err := handleConfigMapFromEnvFileSource(configMap, s.EnvFileSource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return configMap, nil
|
||||
}
|
||||
|
||||
|
@ -121,6 +138,9 @@ func (s ConfigMapGeneratorV1) validate() error {
|
|||
if len(s.Name) == 0 {
|
||||
return fmt.Errorf("name must be specified")
|
||||
}
|
||||
if len(s.EnvFileSource) > 0 && (len(s.FileSources) > 0 || len(s.LiteralSources) > 0) {
|
||||
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -186,6 +206,24 @@ func handleConfigMapFromFileSources(configMap *api.ConfigMap, fileSources []stri
|
|||
return nil
|
||||
}
|
||||
|
||||
// handleConfigMapFromEnvFileSource adds the specified env file source information
|
||||
// into the provided configMap
|
||||
func handleConfigMapFromEnvFileSource(configMap *api.ConfigMap, envFileSource string) error {
|
||||
info, err := os.Stat(envFileSource)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
|
||||
default:
|
||||
return fmt.Errorf("error reading %s: %v", envFileSource, err)
|
||||
}
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("must be a file")
|
||||
}
|
||||
return addFromEnvFileToConfigMap(configMap, envFileSource)
|
||||
}
|
||||
|
||||
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
|
||||
// the value with the content of the given file path, or returns an error.
|
||||
func addKeyFromFileToConfigMap(configMap *api.ConfigMap, keyName, filePath string) error {
|
||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
package kubectl
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -26,6 +28,7 @@ import (
|
|||
|
||||
func TestConfigMapGenerate(t *testing.T) {
|
||||
tests := []struct {
|
||||
setup func(t *testing.T, params map[string]interface{}) func()
|
||||
params map[string]interface{}
|
||||
expected *api.ConfigMap
|
||||
expectErr bool
|
||||
|
@ -107,9 +110,84 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
|
||||
params: map[string]interface{}{
|
||||
"name": "valid_env",
|
||||
"from-env-file": "file.env",
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid_env",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: func() func(t *testing.T, params map[string]interface{}) func() {
|
||||
os.Setenv("g_key1", "1")
|
||||
os.Setenv("g_key2", "2")
|
||||
return setupEnvFile("g_key1", "g_key2=")
|
||||
}(),
|
||||
params: map[string]interface{}{
|
||||
"name": "getenv",
|
||||
"from-env-file": "file.env",
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "getenv",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"g_key1": "1",
|
||||
"g_key2": "",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "too_many_args",
|
||||
"from-literal": []string{"key1=value1"},
|
||||
"from-env-file": "file.env",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile("key.1=value1"),
|
||||
params: map[string]interface{}{
|
||||
"name": "invalid_key",
|
||||
"from-env-file": "file.env",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile(" key1= value1"),
|
||||
params: map[string]interface{}{
|
||||
"name": "with_spaces",
|
||||
"from-env-file": "file.env",
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "with_spaces",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": " value1",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
generator := ConfigMapGeneratorV1{}
|
||||
for _, test := range tests {
|
||||
if test.setup != nil {
|
||||
if teardown := test.setup(t, test.params); teardown != nil {
|
||||
defer teardown()
|
||||
}
|
||||
}
|
||||
obj, err := generator.Generate(test.params)
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
|
@ -122,3 +200,21 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setupEnvFile(lines ...string) func(*testing.T, map[string]interface{}) func() {
|
||||
return func(t *testing.T, params map[string]interface{}) func() {
|
||||
f, err := ioutil.TempFile("", "cme")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
for _, l := range lines {
|
||||
f.WriteString(l)
|
||||
f.WriteString("\r\n")
|
||||
}
|
||||
f.Close()
|
||||
params["from-env-file"] = f.Name()
|
||||
return func() {
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
currentLine := 0
|
||||
utf8bom := []byte{0xEF, 0xBB, 0xBF}
|
||||
for scanner.Scan() {
|
||||
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)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue