mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 06:27:05 +00:00
Add kubectl create configmap
This commit is contained in:
parent
be803eaf64
commit
51a9c2c9ba
@ -823,6 +823,34 @@ __EOF__
|
|||||||
# Clean up
|
# Clean up
|
||||||
kubectl delete namespace test-secrets
|
kubectl delete namespace test-secrets
|
||||||
|
|
||||||
|
######################
|
||||||
|
# ConfigMap #
|
||||||
|
######################
|
||||||
|
|
||||||
|
kubectl create -f docs/user-guide/configmap/config-map.yaml
|
||||||
|
kube::test::get_object_assert configmap "{{range.items}}{{$id_field}}{{end}}" 'test-configmap'
|
||||||
|
kubectl delete configmap test-configmap "${kube_flags[@]}"
|
||||||
|
|
||||||
|
### Create a new namespace
|
||||||
|
# Pre-condition: the test-configmaps namespace does not exist
|
||||||
|
kube::test::get_object_assert 'namespaces' '{{range.items}}{{ if eq $id_field \"test-configmaps\" }}found{{end}}{{end}}:' ':'
|
||||||
|
# Command
|
||||||
|
kubectl create namespace test-configmaps
|
||||||
|
# Post-condition: namespace 'test-configmaps' is created.
|
||||||
|
kube::test::get_object_assert 'namespaces/test-configmaps' "{{$id_field}}" 'test-configmaps'
|
||||||
|
|
||||||
|
### Create a generic configmap in a specific namespace
|
||||||
|
# Pre-condition: no configmaps namespace exists
|
||||||
|
kube::test::get_object_assert 'configmaps --namespace=test-configmaps' "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
# Command
|
||||||
|
kubectl create configmap test-configmap --from-literal=key1=value1 --namespace=test-configmaps
|
||||||
|
# Post-condition: configmap exists and has expected values
|
||||||
|
kube::test::get_object_assert 'configmap/test-configmap --namespace=test-configmaps' "{{$id_field}}" 'test-configmap'
|
||||||
|
[[ "$(kubectl get configmap/test-configmap --namespace=test-configmaps -o yaml "${kube_flags[@]}" | grep 'key1: value1')" ]]
|
||||||
|
# Clean-up
|
||||||
|
kubectl delete configmap test-configmap --namespace=test-configmaps
|
||||||
|
kubectl delete namespace test-configmaps
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Pod templates #
|
# Pod templates #
|
||||||
#################
|
#################
|
||||||
@ -1271,14 +1299,6 @@ __EOF__
|
|||||||
kube::test::get_object_assert rs "{{range.items}}{{$id_field}}:{{end}}" ''
|
kube::test::get_object_assert rs "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||||
|
|
||||||
|
|
||||||
######################
|
|
||||||
# ConfigMap #
|
|
||||||
######################
|
|
||||||
|
|
||||||
kubectl create -f docs/user-guide/configmap/config-map.yaml
|
|
||||||
kube::test::get_object_assert configmap "{{range.items}}{{$id_field}}{{end}}" 'test-configmap'
|
|
||||||
kubectl delete configmap test-configmap "${kube_flags[@]}"
|
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Multiple Resources #
|
# Multiple Resources #
|
||||||
######################
|
######################
|
||||||
|
@ -78,6 +78,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||||||
// create subcommands
|
// create subcommands
|
||||||
cmd.AddCommand(NewCmdCreateNamespace(f, out))
|
cmd.AddCommand(NewCmdCreateNamespace(f, out))
|
||||||
cmd.AddCommand(NewCmdCreateSecret(f, out))
|
cmd.AddCommand(NewCmdCreateSecret(f, out))
|
||||||
|
cmd.AddCommand(NewCmdCreateConfigMap(f, out))
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
96
pkg/kubectl/cmd/create_configmap.go
Normal file
96
pkg/kubectl/cmd/create_configmap.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configMapLong = `Create a configmap based on a file, directory, or specified literal value.
|
||||||
|
|
||||||
|
A single configmap may package one or more key/value pairs.
|
||||||
|
|
||||||
|
When creating a configmap based on a file, the key will default to the basename of the file, and the value will
|
||||||
|
default to the file content. If the basename is an invalid key, you may specify an alternate key.
|
||||||
|
|
||||||
|
When creating a configmap based on a directory, each file whose basename is a valid key in the directory will be
|
||||||
|
packaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories,
|
||||||
|
symlinks, devices, pipes, etc).
|
||||||
|
`
|
||||||
|
|
||||||
|
configMapExample = ` # Create a new configmap named my-config with keys for each file in folder bar
|
||||||
|
$ kubectl create configmap generic my-config --from-file=path/to/bar
|
||||||
|
|
||||||
|
# Create a new configmap named my-config with specified keys instead of names on disk
|
||||||
|
$ kubectl create configmap generic my-config --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub
|
||||||
|
|
||||||
|
# Create a new configMap named my-config with key1=config1 and key2=config2
|
||||||
|
$ kubectl create configmap generic my-config --from-literal=key1=config1 --from-literal=key2=config2`
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigMap is a command to ease creating ConfigMaps.
|
||||||
|
func NewCmdCreateConfigMap(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "configmap NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]",
|
||||||
|
Short: "Create a configMap from a local file, directory or literal value.",
|
||||||
|
Long: configMapLong,
|
||||||
|
Example: configMapExample,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := CreateConfigMap(f, cmdOut, cmd, args)
|
||||||
|
cmdutil.CheckErr(err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||||
|
cmdutil.AddValidateFlags(cmd)
|
||||||
|
cmdutil.AddPrinterFlags(cmd)
|
||||||
|
cmdutil.AddGeneratorFlags(cmd, cmdutil.ConfigMapV1GeneratorName)
|
||||||
|
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid configmap key.")
|
||||||
|
cmd.Flags().StringSlice("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateConfigMap is the implementation of the create configmap generic command.
|
||||||
|
func CreateConfigMap(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
|
||||||
|
name, err := NameFromCommandArgs(cmd, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var generator kubectl.StructuredGenerator
|
||||||
|
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||||
|
case cmdutil.ConfigMapV1GeneratorName:
|
||||||
|
generator = &kubectl.ConfigMapGeneratorV1{
|
||||||
|
Name: name,
|
||||||
|
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||||
|
LiteralSources: cmdutil.GetFlagStringSlice(cmd, "from-literal"),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
|
||||||
|
}
|
||||||
|
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
|
||||||
|
Name: name,
|
||||||
|
StructuredGenerator: generator,
|
||||||
|
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
|
||||||
|
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||||
|
})
|
||||||
|
}
|
54
pkg/kubectl/cmd/create_configmap_test.go
Normal file
54
pkg/kubectl/cmd/create_configmap_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateConfigMap(t *testing.T) {
|
||||||
|
configMap := &api.ConfigMap{}
|
||||||
|
configMap.Name = "my-configmap"
|
||||||
|
f, tf, codec := NewAPIFactory()
|
||||||
|
tf.Printer = &testPrinter{}
|
||||||
|
tf.Client = &fake.RESTClient{
|
||||||
|
Codec: codec,
|
||||||
|
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: 201, Body: objBody(codec, configMap)}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
tf.Namespace = "test"
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
cmd := NewCmdCreateConfigMap(f, buf)
|
||||||
|
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", buf.String(), expectedOutput)
|
||||||
|
}
|
||||||
|
}
|
@ -43,7 +43,7 @@ func NewCmdCreateSecret(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
secretLong = `
|
secretLong = `
|
||||||
Create a secret based on a file, directory, or specified literal value
|
Create a secret based on a file, directory, or specified literal value.
|
||||||
|
|
||||||
A single secret may package one or more key/value pairs.
|
A single secret may package one or more key/value pairs.
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ func NewCmdCreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Comm
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSecretGeneric is the implementation the create secret generic command
|
// CreateSecretGeneric is the implementation of the create secret generic command
|
||||||
func CreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
|
func CreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
|
||||||
name, err := NameFromCommandArgs(cmd, args)
|
name, err := NameFromCommandArgs(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -142,6 +142,7 @@ const (
|
|||||||
NamespaceV1GeneratorName = "namespace/v1"
|
NamespaceV1GeneratorName = "namespace/v1"
|
||||||
SecretV1GeneratorName = "secret/v1"
|
SecretV1GeneratorName = "secret/v1"
|
||||||
SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1"
|
SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1"
|
||||||
|
ConfigMapV1GeneratorName = "configmap/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultGenerators returns the set of default generators for use in Factory instances
|
// DefaultGenerators returns the set of default generators for use in Factory instances
|
||||||
|
212
pkg/kubectl/configmap.go
Normal file
212
pkg/kubectl/configmap.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kubectl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigMapGeneratorV1 supports stable generation of a configMap.
|
||||||
|
type ConfigMapGeneratorV1 struct {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure it supports the generator pattern that uses parameter injection.
|
||||||
|
var _ Generator = &ConfigMapGeneratorV1{}
|
||||||
|
|
||||||
|
// Ensure it supports the generator pattern that uses parameters specified during construction.
|
||||||
|
var _ StructuredGenerator = &ConfigMapGeneratorV1{}
|
||||||
|
|
||||||
|
// Generate returns a configMap using the specified parameters.
|
||||||
|
func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
|
||||||
|
err := ValidateParams(s.ParamNames(), genericParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
delegate := &ConfigMapGeneratorV1{}
|
||||||
|
fromFileStrings, found := genericParams["from-file"]
|
||||||
|
if found {
|
||||||
|
fromFileArray, isArray := fromFileStrings.([]string)
|
||||||
|
if !isArray {
|
||||||
|
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
|
||||||
|
}
|
||||||
|
delegate.FileSources = fromFileArray
|
||||||
|
delete(genericParams, "from-file")
|
||||||
|
}
|
||||||
|
fromLiteralStrings, found := genericParams["from-literal"]
|
||||||
|
if found {
|
||||||
|
fromLiteralArray, isArray := fromLiteralStrings.([]string)
|
||||||
|
if !isArray {
|
||||||
|
return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings)
|
||||||
|
}
|
||||||
|
delegate.LiteralSources = fromLiteralArray
|
||||||
|
delete(genericParams, "from-literal")
|
||||||
|
}
|
||||||
|
params := map[string]string{}
|
||||||
|
for key, value := range genericParams {
|
||||||
|
strVal, isString := value.(string)
|
||||||
|
if !isString {
|
||||||
|
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
|
||||||
|
}
|
||||||
|
params[key] = strVal
|
||||||
|
}
|
||||||
|
delegate.Name = params["name"]
|
||||||
|
delegate.Type = params["type"]
|
||||||
|
return delegate.StructuredGenerate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern.
|
||||||
|
func (s ConfigMapGeneratorV1) ParamNames() []GeneratorParam {
|
||||||
|
return []GeneratorParam{
|
||||||
|
{"name", true},
|
||||||
|
{"type", false},
|
||||||
|
{"from-file", false},
|
||||||
|
{"from-literal", false},
|
||||||
|
{"force", false},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructuredGenerate outputs a configMap object using the configured fields.
|
||||||
|
func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) {
|
||||||
|
if err := s.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configMap := &api.ConfigMap{}
|
||||||
|
configMap.Name = s.Name
|
||||||
|
configMap.Data = map[string]string{}
|
||||||
|
if len(s.FileSources) > 0 {
|
||||||
|
if err := handleConfigMapFromFileSources(configMap, s.FileSources); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s.LiteralSources) > 0 {
|
||||||
|
if err := handleConfigMapFromLiteralSources(configMap, s.LiteralSources); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return configMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate validates required fields are set to support structured generation.
|
||||||
|
func (s ConfigMapGeneratorV1) validate() error {
|
||||||
|
if len(s.Name) == 0 {
|
||||||
|
return fmt.Errorf("name must be specified")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleConfigMapFromLiteralSources adds the specified literal source
|
||||||
|
// information into the provided configMap.
|
||||||
|
func handleConfigMapFromLiteralSources(configMap *api.ConfigMap, literalSources []string) error {
|
||||||
|
for _, literalSource := range literalSources {
|
||||||
|
keyName, value, err := 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 *api.ConfigMap, fileSources []string) error {
|
||||||
|
for _, fileSource := range fileSources {
|
||||||
|
keyName, filePath, err := 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 fmt.Errorf("error reading %s: %v", filePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
err = addKeyFromFileToConfigMap(configMap, keyName, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
|
||||||
|
// the value with the content of the given file path, or returns an error.
|
||||||
|
func addKeyFromFileToConfigMap(configMap *api.ConfigMap, keyName, filePath string) error {
|
||||||
|
data, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *api.ConfigMap, keyName, data string) error {
|
||||||
|
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys
|
||||||
|
// to be consistent; validation.IsSecretKey is used here intentionally.
|
||||||
|
if !validation.IsSecretKey(keyName) {
|
||||||
|
return fmt.Errorf("%v is not a valid key name for a configMap", keyName)
|
||||||
|
}
|
||||||
|
if _, entryExists := configMap.Data[keyName]; entryExists {
|
||||||
|
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, configMap.Data)
|
||||||
|
}
|
||||||
|
configMap.Data[keyName] = data
|
||||||
|
return nil
|
||||||
|
}
|
108
pkg/kubectl/configmap_test.go
Normal file
108
pkg/kubectl/configmap_test.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kubectl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigMapGenerate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
params map[string]interface{}
|
||||||
|
expected *api.ConfigMap
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
params: map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
},
|
||||||
|
expected: &api.ConfigMap{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Data: map[string]string{},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
"type": "my-type",
|
||||||
|
},
|
||||||
|
expected: &api.ConfigMap{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Data: map[string]string{},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
"from-literal": []string{"key1=value1", "key2=value2"},
|
||||||
|
},
|
||||||
|
expected: &api.ConfigMap{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
"from-literal": []string{"key1value1"},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
"from-file": []string{"key1=/file=2"},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: map[string]interface{}{
|
||||||
|
"name": "foo",
|
||||||
|
"from-file": []string{"key1==value"},
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
generator := ConfigMapGeneratorV1{}
|
||||||
|
for _, test := range tests {
|
||||||
|
obj, err := generator.Generate(test.params)
|
||||||
|
if !test.expectErr && err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if test.expectErr && err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(obj.(*api.ConfigMap), test.expected) {
|
||||||
|
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.ConfigMap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,9 @@ limitations under the License.
|
|||||||
package kubectl
|
package kubectl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
@ -120,3 +123,36 @@ func expandResourceShortcut(resource unversioned.GroupVersionResource) unversion
|
|||||||
}
|
}
|
||||||
return resource
|
return resource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseFileSource parses the source given. Acceptable formats include:
|
||||||
|
//
|
||||||
|
// 1. source-path: the basename will become the key name
|
||||||
|
// 2. source-name=source-path: the source-name will become the key name and source-path is the path to the key file
|
||||||
|
//
|
||||||
|
// Key names cannot include '='.
|
||||||
|
func parseFileSource(source string) (keyName, filePath string, err error) {
|
||||||
|
numSeparators := strings.Count(source, "=")
|
||||||
|
switch {
|
||||||
|
case numSeparators == 0:
|
||||||
|
return path.Base(source), source, nil
|
||||||
|
case numSeparators == 1 && strings.HasPrefix(source, "="):
|
||||||
|
return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "="))
|
||||||
|
case numSeparators == 1 && strings.HasSuffix(source, "="):
|
||||||
|
return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "="))
|
||||||
|
case numSeparators > 1:
|
||||||
|
return "", "", errors.New("Key names or file paths cannot contain '='.")
|
||||||
|
default:
|
||||||
|
components := strings.Split(source, "=")
|
||||||
|
return components[0], components[1], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLiteralSource parses the source key=val pair
|
||||||
|
func parseLiteralSource(source string) (keyName, value string, err error) {
|
||||||
|
items := strings.Split(source, "=")
|
||||||
|
if len(items) != 2 {
|
||||||
|
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items[0], items[1], nil
|
||||||
|
}
|
||||||
|
194
pkg/kubectl/kubectl_test.go
Normal file
194
pkg/kubectl/kubectl_test.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kubectl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFileSource(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
key string
|
||||||
|
filepath string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success 1",
|
||||||
|
input: "boo=zoo",
|
||||||
|
key: "boo",
|
||||||
|
filepath: "zoo",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success 2",
|
||||||
|
input: "boo=/path/to/zoo",
|
||||||
|
key: "boo",
|
||||||
|
filepath: "/path/to/zoo",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success 3",
|
||||||
|
input: "boo-2=/1/2/3/4/5/zab.txt",
|
||||||
|
key: "boo-2",
|
||||||
|
filepath: "/1/2/3/4/5/zab.txt",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success 4",
|
||||||
|
input: "boo-=this/seems/weird.txt",
|
||||||
|
key: "boo-",
|
||||||
|
filepath: "this/seems/weird.txt",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success 5",
|
||||||
|
input: "-key=some/path",
|
||||||
|
key: "-key",
|
||||||
|
filepath: "some/path",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 1",
|
||||||
|
input: "key==some/path",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 2",
|
||||||
|
input: "=key=some/path",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 3",
|
||||||
|
input: "==key=/some/other/path",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 4",
|
||||||
|
input: "=key",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid 5",
|
||||||
|
input: "key=",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
key, filepath, err := parseFileSource(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
if tc.err {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("%v: unexpected error: %v", tc.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.err {
|
||||||
|
t.Errorf("%v: unexpected success", tc.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := tc.key, key; e != a {
|
||||||
|
t.Errorf("%v: expected key %v; got %v", tc.name, e, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := tc.filepath, filepath; e != a {
|
||||||
|
t.Errorf("%v: expected filepath %v; got %v", tc.name, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLiteralSource(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
key string
|
||||||
|
value string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success 1",
|
||||||
|
input: "key=value",
|
||||||
|
key: "key",
|
||||||
|
value: "value",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success 2",
|
||||||
|
input: "key=value/with/slashes",
|
||||||
|
key: "key",
|
||||||
|
value: "value/with/slashes",
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "err 1",
|
||||||
|
input: "key==value",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "err 2",
|
||||||
|
input: "key=value=",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "err 3",
|
||||||
|
input: "key2=value==",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "err 4",
|
||||||
|
input: "==key",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "err 5",
|
||||||
|
input: "=key=",
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
key, value, err := parseLiteralSource(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
if tc.err {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("%v: unexpected error: %v", tc.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.err {
|
||||||
|
t.Errorf("%v: unexpected success", tc.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := tc.key, key; e != a {
|
||||||
|
t.Errorf("%v: expected key %v; got %v", tc.name, e, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := tc.value, value; e != a {
|
||||||
|
t.Errorf("%v: expected value %v; got %v", tc.name, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
package kubectl
|
package kubectl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -206,33 +205,3 @@ func addKeyFromLiteralToSecret(secret *api.Secret, keyName string, data []byte)
|
|||||||
secret.Data[keyName] = data
|
secret.Data[keyName] = data
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseFileSource parses the source given. Acceptable formats include:
|
|
||||||
// source-name=source-path, where source-name will become the key name and source-path is the path to the key file
|
|
||||||
// source-path, where source-path is a path to a file or directory, and key names will default to file names
|
|
||||||
// Key names cannot include '='.
|
|
||||||
func parseFileSource(source string) (keyName, filePath string, err error) {
|
|
||||||
numSeparators := strings.Count(source, "=")
|
|
||||||
switch {
|
|
||||||
case numSeparators == 0:
|
|
||||||
return path.Base(source), source, nil
|
|
||||||
case numSeparators == 1 && strings.HasPrefix(source, "="):
|
|
||||||
return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "="))
|
|
||||||
case numSeparators == 1 && strings.HasSuffix(source, "="):
|
|
||||||
return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "="))
|
|
||||||
case numSeparators > 1:
|
|
||||||
return "", "", errors.New("Key names or file paths cannot contain '='.")
|
|
||||||
default:
|
|
||||||
components := strings.Split(source, "=")
|
|
||||||
return components[0], components[1], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseLiteralSource parses the source key=val pair
|
|
||||||
func parseLiteralSource(source string) (keyName, value string, err error) {
|
|
||||||
items := strings.Split(source, "=")
|
|
||||||
if len(items) != 2 {
|
|
||||||
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
|
||||||
}
|
|
||||||
return items[0], items[1], nil
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user