Remove dependency of generators from create configmap

Kubernetes-commit: 838b189130e1a7f19b11a0764f15df1c8de32769
This commit is contained in:
Chok Yip Lau 2021-01-10 18:47:32 -05:00 committed by Kubernetes Publisher
parent 113a615ab2
commit 31e15a5c73
6 changed files with 786 additions and 84 deletions

View File

@ -17,12 +17,27 @@ limitations under the License.
package create package create
import ( import (
"context"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"unicode/utf8"
"github.com/spf13/cobra" "github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate" "k8s.io/kubectl/pkg/scheme"
generateversioned "k8s.io/kubectl/pkg/generate/versioned" "k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
) )
@ -57,16 +72,50 @@ var (
kubectl create configmap my-config --from-env-file=path/to/bar.env`)) kubectl create configmap my-config --from-env-file=path/to/bar.env`))
) )
// ConfigMapOpts holds properties for create configmap sub-command // ConfigMapOptions holds properties for create configmap sub-command
type ConfigMapOpts struct { type ConfigMapOptions struct {
CreateSubcommandOptions *CreateSubcommandOptions // PrintFlags holds options necessary for obtaining a printer
PrintFlags *genericclioptions.PrintFlags
PrintObj func(obj runtime.Object) error
// Name of configMap (required)
Name string
// Type of configMap (optional)
Type string
// FileSources to derive the configMap from (optional)
FileSources []string
// LiteralSources to derive the configMap from (optional)
LiteralSources []string
// EnvFileSource to derive the configMap from (optional)
EnvFileSource string
// AppendHash; if true, derive a hash from the ConfigMap and append it to the name
AppendHash bool
FieldManager string
CreateAnnotation bool
Namespace string
EnforceNamespace bool
Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.DryRunVerifier
genericclioptions.IOStreams
} }
// NewCmdCreateConfigMap initializes and returns ConfigMapOpts // NewConfigMapOptions creates a new *ConfigMapOptions with default value
func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { func NewConfigMapOptions(ioStreams genericclioptions.IOStreams) *ConfigMapOptions {
options := &ConfigMapOpts{ return &ConfigMapOptions{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams), FileSources: []string{},
LiteralSources: []string{},
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
} }
}
// NewCmdCreateConfigMap creates the `create configmap` Cobra command
func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewConfigMapOptions(ioStreams)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]", Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]",
@ -76,49 +125,296 @@ func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericclioptions.IOStre
Long: configMapLong, Long: configMapLong,
Example: configMapExample, Example: configMapExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args)) cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run()) cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
}, },
} }
o.PrintFlags.AddFlags(cmd)
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, generateversioned.ConfigMapV1GeneratorName) cmdutil.AddDryRunFlag(cmd)
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().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "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().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).") cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
cmd.Flags().Bool("append-hash", false, "Append a hash of the configmap to its name.") cmd.Flags().StringVar(&o.EnvFileSource, "from-env-file", o.EnvFileSource, "Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create") cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the configmap to its name.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
return cmd return cmd
} }
// Complete completes all the required options // Complete loads data from the command line environment
func (o *ConfigMapOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { func (o *ConfigMapOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args) var err error
o.Name, err = NameFromCommandArgs(cmd, args)
if err != nil { if err != nil {
return err return err
} }
var generator generate.StructuredGenerator restConfig, err := f.ToRESTConfig()
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { if err != nil {
case generateversioned.ConfigMapV1GeneratorName: return err
generator = &generateversioned.ConfigMapGeneratorV1{
Name: name,
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
} }
o.Client, err = corev1client.NewForConfig(restConfig)
if err != nil {
return err
}
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
return nil
}
// Validate checks if ConfigMapOptions has sufficient value to run
func (o *ConfigMapOptions) Validate() error {
if len(o.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(o.EnvFileSource) > 0 && (len(o.FileSources) > 0 || len(o.LiteralSources) > 0) {
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
}
return nil
}
// Run calls createConfigMap and filled in value for configMap object
func (o *ConfigMapOptions) Run() error {
configMap, err := o.createConfigMap()
if err != nil {
return nil
}
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, configMap, scheme.DefaultJSONEncoder()); err != nil {
return err
}
if o.DryRunStrategy != cmdutil.DryRunClient {
createOptions := metav1.CreateOptions{}
if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager
}
if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(configMap.GroupVersionKind()); err != nil {
return err
}
createOptions.DryRun = []string{metav1.DryRunAll}
}
configMap, err = o.Client.ConfigMaps(o.Namespace).Create(context.TODO(), configMap, createOptions)
if err != nil {
return fmt.Errorf("failed to create configmap: %v", err)
}
}
return o.PrintObj(configMap)
}
// createConfigMap fills in key value pair from the information given in
// ConfigMapOptions into *corev1.ConfigMap
func (o *ConfigMapOptions) createConfigMap() (*corev1.ConfigMap, error) {
namespace := ""
if o.EnforceNamespace {
namespace = o.Namespace
}
configMap := &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
Namespace: namespace,
},
}
configMap.Name = o.Name
configMap.Data = map[string]string{}
configMap.BinaryData = map[string][]byte{}
if len(o.FileSources) > 0 {
if err := handleConfigMapFromFileSources(configMap, o.FileSources); err != nil {
return nil, err
}
}
if len(o.LiteralSources) > 0 {
if err := handleConfigMapFromLiteralSources(configMap, o.LiteralSources); err != nil {
return nil, err
}
}
if len(o.EnvFileSource) > 0 {
if err := handleConfigMapFromEnvFileSource(configMap, o.EnvFileSource); err != nil {
return nil, err
}
}
if o.AppendHash {
hash, err := hash.ConfigMapHash(configMap)
if err != nil {
return nil, err
}
configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, hash)
}
return configMap, nil
}
// handleConfigMapFromLiteralSources adds the specified literal source
// information into the provided configMap.
func handleConfigMapFromLiteralSources(configMap *corev1.ConfigMap, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := util.ParseLiteralSource(literalSource)
if err != nil {
return err
}
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
if err != nil {
return err
}
}
return nil
}
// handleConfigMapFromFileSources adds the specified file source information
// into the provided configMap
func handleConfigMapFromFileSources(configMap *corev1.ConfigMap, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := util.ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default: default:
return errUnsupportedGenerator(cmd, generatorName) return fmt.Errorf("error reading %s: %v", filePath, err)
} }
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator) }
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
if err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
return err
} }
// Run performs the execution of 'create' sub command options }
func (o *ConfigMapOpts) Run() error { }
return o.CreateSubcommandOptions.Run() return nil
}
// handleConfigMapFromEnvFileSource adds the specified env file source information
// into the provided configMap
func handleConfigMapFromEnvFileSource(configMap *corev1.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("env config file cannot be a directory")
}
return cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value)
})
}
// 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 *corev1.ConfigMap, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
if utf8.Valid(data) {
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
}
err = validateNewConfigMap(configMap, keyName)
if err != nil {
return err
}
configMap.BinaryData[keyName] = data
return nil
}
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
// returning an error if the key is not valid or if the key already exists.
func addKeyFromLiteralToConfigMap(configMap *corev1.ConfigMap, keyName, data string) error {
err := validateNewConfigMap(configMap, keyName)
if err != nil {
return err
}
configMap.Data[keyName] = data
return nil
}
// validateNewConfigMap checks whether the keyname is valid
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
func validateNewConfigMap(configMap *corev1.ConfigMap, keyName string) error {
if errs := validation.IsConfigMapKey(keyName); len(errs) > 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ","))
}
if _, exists := configMap.Data[keyName]; exists {
return fmt.Errorf("cannot add key %q, another key by that name already exists in Data for ConfigMap %q", keyName, configMap.Name)
}
if _, exists := configMap.BinaryData[keyName]; exists {
return fmt.Errorf("cannot add key %q, another key by that name already exists in BinaryData for ConfigMap %q", keyName, configMap.Name)
}
return nil
} }

View File

@ -17,45 +17,449 @@ limitations under the License.
package create package create
import ( import (
"net/http" "io/ioutil"
"os"
"testing" "testing"
"k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema" apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/cli-runtime/pkg/genericclioptions" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"k8s.io/kubectl/pkg/scheme"
) )
func TestCreateConfigMap(t *testing.T) { func TestCreateConfigMap(t *testing.T) {
configMap := &v1.ConfigMap{} tests := map[string]struct {
configMap.Name = "my-configmap" configMapName string
tf := cmdtesting.NewTestFactory().WithNamespace("test") configMapType string
defer tf.Cleanup() appendHash bool
fromLiteral []string
fromFile []string
fromEnvFile string
setup func(t *testing.T, configMapOptions *ConfigMapOptions) func()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) expected *corev1.ConfigMap
ns := scheme.Codecs.WithoutConversion() expectErr bool
}{
"create_foo_configmap": {
configMapName: "foo",
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_hash_configmap": {
configMapName: "foo",
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-867km9574f",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_type_configmap": {
configMapName: "foo",
configMapType: "my-type",
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_type_hash_configmap": {
configMapName: "foo",
configMapType: "my-type",
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-867km9574f",
},
Data: map[string]string{},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_two_literal_configmap": {
configMapName: "foo",
fromLiteral: []string{"key1=value1", "key2=value2"},
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_two_literal_hash_configmap": {
configMapName: "foo",
fromLiteral: []string{"key1=value1", "key2=value2"},
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-gcb75dd9gb",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_key1_=value1_configmap": {
configMapName: "foo",
fromLiteral: []string{"key1==value1"},
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{
"key1": "=value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_key1_=value1_hash_configmap": {
configMapName: "foo",
fromLiteral: []string{"key1==value1"},
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-bdgk9ttt7m",
},
Data: map[string]string{
"key1": "=value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_from_file_foo1_foo2_configmap": {
configMapName: "foo",
setup: setupBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}, "foo1", "foo2"),
fromFile: []string{"foo1", "foo2"},
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{
"foo1": "hello world",
"foo2": "hello world",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_foo_from_file_foo1_foo2_and_configmap": {
configMapName: "foo",
setup: setupBinaryFile([]byte{0xff, 0xfd}, "foo1", "foo2"),
fromFile: []string{"foo1", "foo2"},
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string]string{},
BinaryData: map[string][]byte{
"foo1": {0xff, 0xfd},
"foo2": {0xff, 0xfd},
},
},
expectErr: false,
},
"create_valid_env_from_env_file_configmap": {
configMapName: "valid_env",
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
fromEnvFile: "file.env",
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_valid_env_from_env_file_hash_configmap": {
configMapName: "valid_env",
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
fromEnvFile: "file.env",
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env-2cgh8552ch",
},
Data: map[string]string{
"key1": "value1",
"key2": "value2",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_get_env_from_env_file_configmap": {
configMapName: "get_env",
setup: func() func(t *testing.T, configMapOptions *ConfigMapOptions) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
fromEnvFile: "file.env",
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "get_env",
},
Data: map[string]string{
"g_key1": "1",
"g_key2": "",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_get_env_from_env_file_hash_configmap": {
configMapName: "get_env",
setup: func() func(t *testing.T, configMapOptions *ConfigMapOptions) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupEnvFile("g_key1", "g_key2=")
}(),
fromEnvFile: "file.env",
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "get_env-54k882kkd2",
},
Data: map[string]string{
"g_key1": "1",
"g_key2": "",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_value_with_space_from_env_file_configmap": {
configMapName: "value_with_space",
setup: setupEnvFile("key1= value1"),
fromEnvFile: "file.env",
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "value_with_space",
},
Data: map[string]string{
"key1": " value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_value_with_space_from_env_file_hash_configmap": {
configMapName: "valid_with_space",
setup: setupEnvFile("key1= value1"),
fromEnvFile: "file.env",
appendHash: true,
expected: &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_with_space-b4448m7gdm",
},
Data: map[string]string{
"key1": " value1",
},
BinaryData: map[string][]byte{},
},
expectErr: false,
},
"create_invalid_configmap_filepath_contains_=": {
configMapName: "foo",
fromFile: []string{"key1=/file=2"},
expectErr: true,
},
"create_invalid_configmap_filepath_key_contains_=": {
configMapName: "foo",
fromFile: []string{"=key=/file1"},
expectErr: true,
},
"create_invalid_configmap_literal_key_contains_=": {
configMapName: "foo",
fromFile: []string{"=key=value1"},
expectErr: true,
},
"create_invalid_configmap_duplicate_key1": {
configMapName: "foo",
fromLiteral: []string{"key1=value1", "key1=value2"},
expectErr: true,
},
"create_invalid_configmap_no_file": {
configMapName: "foo",
fromFile: []string{"key1=/file1"},
expectErr: true,
},
"create_invalid_configmap_invalid_literal": {
configMapName: "foo",
fromLiteral: []string{"key1value1"},
expectErr: true,
},
"create_invalid_configmap_invalid_filepath": {
configMapName: "foo",
fromFile: []string{"key1==file1"},
expectErr: true,
},
"create_invalid_configmap_too_many_args": {
configMapName: "too_many_args",
fromFile: []string{"key1=/file1"},
fromEnvFile: "foo",
expectErr: true,
},
"create_invalid_configmap_too_many_args_1": {
configMapName: "too_many_args_1",
fromLiteral: []string{"key1=value1"},
fromEnvFile: "foo",
expectErr: true,
},
}
tf.Client = &fake.RESTClient{ // run all the tests
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"}, for name, test := range tests {
NegotiatedSerializer: ns, t.Run(name, func(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { configMapOptions := ConfigMapOptions{
switch p, m := req.URL.Path, req.Method; { Name: test.configMapName,
case p == "/namespaces/test/configmaps" && m == "POST": Type: test.configMapType,
return &http.Response{StatusCode: http.StatusCreated, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, configMap)}, nil AppendHash: test.appendHash,
default: FileSources: test.fromFile,
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) LiteralSources: test.fromLiteral,
return nil, nil EnvFileSource: test.fromEnvFile,
}
if test.setup != nil {
if teardown := test.setup(t, &configMapOptions); teardown != nil {
defer teardown()
}
}
configMap, err := configMapOptions.createConfigMap()
if !test.expectErr && err != nil {
t.Errorf("test %s, unexpected error: %v", name, err)
}
if test.expectErr && err == nil {
t.Errorf("test %s was expecting an error but no error occurred", name)
}
if !apiequality.Semantic.DeepEqual(configMap, test.expected) {
t.Errorf("test %s expected:\n%#v\ngot:\n%#v", name, test.expected, configMap)
}
})
}
}
func setupEnvFile(lines ...string) func(*testing.T, *ConfigMapOptions) func() {
return func(t *testing.T, configMapOptions *ConfigMapOptions) 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()
configMapOptions.EnvFileSource = f.Name()
return func() {
os.Remove(f.Name())
}
}
}
func setupBinaryFile(data []byte, files ...string) func(*testing.T, *ConfigMapOptions) func() {
return func(t *testing.T, configMapOptions *ConfigMapOptions) func() {
tmp, _ := ioutil.TempDir("", "")
for i, file := range files {
f := tmp + "/" + file
ioutil.WriteFile(f, data, 0644)
configMapOptions.FileSources[i] = f
}
return func() {
for _, file := range files {
f := tmp + "/" + file
os.Remove(f)
}
} }
}),
}
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateConfigMap(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{configMap.Name})
expectedOutput := "configmap/" + configMap.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
} }
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package versioned package util
import ( import (
"bufio" "bufio"
@ -30,9 +30,9 @@ import (
var utf8bom = []byte{0xEF, 0xBB, 0xBF} var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// proccessEnvFileLine returns a blank key if the line is empty or a comment. // processEnvFileLine returns a blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary. // The value will be retrieved from the environment if necessary.
func proccessEnvFileLine(line []byte, filePath string, func processEnvFileLine(line []byte, filePath string,
currentLine int) (key, value string, err error) { currentLine int) (key, value string, err error) {
if !utf8.Valid(line) { if !utf8.Valid(line) {
@ -69,9 +69,9 @@ func proccessEnvFileLine(line []byte, filePath string,
return return
} }
// addFromEnvFile processes an env file allows a generic addTo to handle the // AddFromEnvFile processes an env file allows a generic addTo to handle the
// collection of key value pairs or returns an error. // collection of key value pairs or returns an error.
func addFromEnvFile(filePath string, addTo func(key, value string) error) error { func AddFromEnvFile(filePath string, addTo func(key, value string) error) error {
f, err := os.Open(filePath) f, err := os.Open(filePath)
if err != nil { if err != nil {
return err return err
@ -84,7 +84,7 @@ func addFromEnvFile(filePath string, addTo func(key, value string) error) error
// Process the current line, retrieving a key/value pair if // Process the current line, retrieving a key/value pair if
// possible. // possible.
scannedBytes := scanner.Bytes() scannedBytes := scanner.Bytes()
key, value, err := proccessEnvFileLine(scannedBytes, filePath, currentLine) key, value, err := processEnvFileLine(scannedBytes, filePath, currentLine)
if err != nil { if err != nil {
return err return err
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package versioned package util
import ( import (
"os" "os"
@ -50,7 +50,7 @@ func Test_processEnvFileLine(t *testing.T) {
} }
for _, tt := range testCases { for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
key, value, err := proccessEnvFileLine(tt.line, `filename`, tt.currentLine) key, value, err := processEnvFileLine(tt.line, `filename`, tt.currentLine)
t.Logf("Testing that %s.", tt.name) t.Logf("Testing that %s.", tt.name)
if tt.expectedKey != key { if tt.expectedKey != key {
t.Errorf("\texpected key %q, received %q", tt.expectedKey, key) t.Errorf("\texpected key %q, received %q", tt.expectedKey, key)
@ -92,7 +92,7 @@ func Test_processEnvFileLine_readEnvironment(t *testing.T) {
os.Setenv(realKey, `my_value`) os.Setenv(realKey, `my_value`)
key, value, err := proccessEnvFileLine([]byte(realKey), `filename`, 3) key, value, err := processEnvFileLine([]byte(realKey), `filename`, 3)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -24,9 +24,10 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate" "k8s.io/kubectl/pkg/generate"
"k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash" "k8s.io/kubectl/pkg/util/hash"
@ -246,7 +247,7 @@ func handleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource str
return fmt.Errorf("env config file cannot be a directory") return fmt.Errorf("env config file cannot be a directory")
} }
return addFromEnvFile(envFileSource, func(key, value string) error { return cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value) return addKeyFromLiteralToConfigMap(configMap, key, value)
}) })
} }

View File

@ -23,9 +23,10 @@ import (
"path" "path"
"strings" "strings"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate" "k8s.io/kubectl/pkg/generate"
"k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash" "k8s.io/kubectl/pkg/util/hash"
@ -246,7 +247,7 @@ func handleFromEnvFileSource(secret *v1.Secret, envFileSource string) error {
return fmt.Errorf("env secret file cannot be a directory") return fmt.Errorf("env secret file cannot be a directory")
} }
return addFromEnvFile(envFileSource, func(key, value string) error { return cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value)) return addKeyFromLiteralToSecret(secret, key, []byte(value))
}) })
} }