Remove dependency of generators from create configmap
Kubernetes-commit: 838b189130e1a7f19b11a0764f15df1c8de32769
This commit is contained in:
parent
113a615ab2
commit
31e15a5c73
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue