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
import (
"context"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"unicode/utf8"
"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/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate"
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
)
@ -57,16 +72,50 @@ var (
kubectl create configmap my-config --from-env-file=path/to/bar.env`))
)
// ConfigMapOpts holds properties for create configmap sub-command
type ConfigMapOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
// ConfigMapOptions holds properties for create configmap sub-command
type ConfigMapOptions struct {
// 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
func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &ConfigMapOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
// NewConfigMapOptions creates a new *ConfigMapOptions with default value
func NewConfigMapOptions(ioStreams genericclioptions.IOStreams) *ConfigMapOptions {
return &ConfigMapOptions{
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{
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,
Example: configMapExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, generateversioned.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).")
cmd.Flags().Bool("append-hash", false, "Append a hash of the configmap to its name.")
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create")
cmdutil.AddDryRunFlag(cmd)
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().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
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).")
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
}
// Complete completes all the required options
func (o *ConfigMapOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
// Complete loads data from the command line environment
func (o *ConfigMapOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Name, err = NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator generate.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case generateversioned.ConfigMapV1GeneratorName:
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"),
restConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
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:
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
import (
"net/http"
"io/ioutil"
"os"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"k8s.io/kubectl/pkg/scheme"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestCreateConfigMap(t *testing.T) {
configMap := &v1.ConfigMap{}
configMap.Name = "my-configmap"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
tests := map[string]struct {
configMapName string
configMapType string
appendHash bool
fromLiteral []string
fromFile []string
fromEnvFile string
setup func(t *testing.T, configMapOptions *ConfigMapOptions) func()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs.WithoutConversion()
expected *corev1.ConfigMap
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{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/configmaps" && m == "POST":
return &http.Response{StatusCode: http.StatusCreated, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, configMap)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
// run all the tests
for name, test := range tests {
t.Run(name, func(t *testing.T) {
configMapOptions := ConfigMapOptions{
Name: test.configMapName,
Type: test.configMapType,
AppendHash: test.appendHash,
FileSources: test.fromFile,
LiteralSources: test.fromLiteral,
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.
*/
package versioned
package util
import (
"bufio"
@ -30,9 +30,9 @@ import (
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.
func proccessEnvFileLine(line []byte, filePath string,
func processEnvFileLine(line []byte, filePath string,
currentLine int) (key, value string, err error) {
if !utf8.Valid(line) {
@ -69,9 +69,9 @@ func proccessEnvFileLine(line []byte, filePath string,
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.
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)
if err != nil {
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
// possible.
scannedBytes := scanner.Bytes()
key, value, err := proccessEnvFileLine(scannedBytes, filePath, currentLine)
key, value, err := processEnvFileLine(scannedBytes, filePath, currentLine)
if err != nil {
return err
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package versioned
package util
import (
"os"
@ -50,7 +50,7 @@ func Test_processEnvFileLine(t *testing.T) {
}
for _, tt := range testCases {
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)
if 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`)
key, value, err := proccessEnvFileLine([]byte(realKey), `filename`, 3)
key, value, err := processEnvFileLine([]byte(realKey), `filename`, 3)
if err != nil {
t.Fatal(err)
}

View File

@ -24,9 +24,10 @@ import (
"strings"
"unicode/utf8"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate"
"k8s.io/kubectl/pkg/util"
"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 addFromEnvFile(envFileSource, func(key, value string) error {
return cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value)
})
}

View File

@ -23,9 +23,10 @@ import (
"path"
"strings"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate"
"k8s.io/kubectl/pkg/util"
"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 addFromEnvFile(envFileSource, func(key, value string) error {
return cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value))
})
}