Merge pull request #20777 from pmorie/kubectl-create-configmap

Auto commit by PR queue bot
pull/6/head
k8s-merge-robot 2016-02-19 00:03:53 -08:00
commit 5a3dec8dad
19 changed files with 1129 additions and 46 deletions

View File

@ -22,6 +22,7 @@ docs/man/man1/kubectl-config-view.1
docs/man/man1/kubectl-config.1
docs/man/man1/kubectl-convert.1
docs/man/man1/kubectl-cordon.1
docs/man/man1/kubectl-create-configmap.1
docs/man/man1/kubectl-create-namespace.1
docs/man/man1/kubectl-create-secret-docker-registry.1
docs/man/man1/kubectl-create-secret-generic.1
@ -73,6 +74,7 @@ docs/user-guide/kubectl/kubectl_config_view.md
docs/user-guide/kubectl/kubectl_convert.md
docs/user-guide/kubectl/kubectl_cordon.md
docs/user-guide/kubectl/kubectl_create.md
docs/user-guide/kubectl/kubectl_create_configmap.md
docs/user-guide/kubectl/kubectl_create_namespace.md
docs/user-guide/kubectl/kubectl_create_secret.md
docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md

View File

@ -621,12 +621,69 @@ _kubectl_create_secret()
must_have_one_noun=()
}
_kubectl_create_configmap()
{
last_command="kubectl_create_configmap"
commands=()
flags=()
two_word_flags=()
flags_with_completion=()
flags_completion=()
flags+=("--dry-run")
flags+=("--from-file=")
flags+=("--from-literal=")
flags+=("--generator=")
flags+=("--no-headers")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--output-version=")
flags+=("--save-config")
flags+=("--schema-cache-dir=")
flags+=("--show-all")
flags+=("-a")
flags+=("--show-labels")
flags+=("--sort-by=")
flags+=("--template=")
two_word_flags+=("-t")
flags+=("--validate")
flags+=("--alsologtostderr")
flags+=("--api-version=")
flags+=("--certificate-authority=")
flags+=("--client-certificate=")
flags+=("--client-key=")
flags+=("--cluster=")
flags+=("--context=")
flags+=("--insecure-skip-tls-verify")
flags+=("--kubeconfig=")
flags+=("--log-backtrace-at=")
flags+=("--log-dir=")
flags+=("--log-flush-frequency=")
flags+=("--logtostderr")
flags+=("--match-server-version")
flags+=("--namespace=")
flags+=("--password=")
flags+=("--server=")
two_word_flags+=("-s")
flags+=("--stderrthreshold=")
flags+=("--token=")
flags+=("--user=")
flags+=("--username=")
flags+=("--v=")
flags+=("--vmodule=")
must_have_one_flag=()
must_have_one_noun=()
}
_kubectl_create()
{
last_command="kubectl_create"
commands=()
commands+=("namespace")
commands+=("secret")
commands+=("configmap")
flags=()
two_word_flags=()

View File

@ -0,0 +1,211 @@
.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" ""
.SH NAME
.PP
kubectl create configmap \- Create a configMap from a local file, directory or literal value.
.SH SYNOPSIS
.PP
\fBkubectl create configmap\fP [OPTIONS]
.SH DESCRIPTION
.PP
Create a configmap based on a file, directory, or specified literal value.
.PP
A single configmap may package one or more key/value pairs.
.PP
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.
.PP
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).
.SH OPTIONS
.PP
\fB\-\-dry\-run\fP=false
If true, only print the object that would be sent, without sending it.
.PP
\fB\-\-from\-file\fP=[]
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.
.PP
\fB\-\-from\-literal\fP=[]
Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)
.PP
\fB\-\-generator\fP="configmap/v1"
The name of the API generator to use.
.PP
\fB\-\-no\-headers\fP=false
When using the default output, don't print headers.
.PP
\fB\-o\fP, \fB\-\-output\fP=""
Output format. One of: json|yaml|wide|name|go\-template=...|go\-template\-file=...|jsonpath=...|jsonpath\-file=... See golang template [
\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]] and jsonpath template [
\[la]http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md\[ra]].
.PP
\fB\-\-output\-version\fP=""
Output the formatted object with the given version (default api\-version).
.PP
\fB\-\-save\-config\fP=false
If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future.
.PP
\fB\-\-schema\-cache\-dir\fP="\~/.kube/schema"
If non\-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema'
.PP
\fB\-a\fP, \fB\-\-show\-all\fP=false
When printing, show all resources (default hide terminated pods.)
.PP
\fB\-\-show\-labels\fP=false
When printing, show all labels as the last column (default hide labels column)
.PP
\fB\-\-sort\-by\fP=""
If non\-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.
.PP
\fB\-\-template\fP=""
Template string or path to template file to use when \-o=go\-template, \-o=go\-template\-file. The template format is golang templates [
\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]].
.PP
\fB\-\-validate\fP=true
If true, use a schema to validate the input before sending it
.SH OPTIONS INHERITED FROM PARENT COMMANDS
.PP
\fB\-\-alsologtostderr\fP=false
log to standard error as well as files
.PP
\fB\-\-api\-version\fP=""
The API version to use when talking to the server
.PP
\fB\-\-certificate\-authority\fP=""
Path to a cert. file for the certificate authority.
.PP
\fB\-\-client\-certificate\fP=""
Path to a client certificate file for TLS.
.PP
\fB\-\-client\-key\fP=""
Path to a client key file for TLS.
.PP
\fB\-\-cluster\fP=""
The name of the kubeconfig cluster to use
.PP
\fB\-\-context\fP=""
The name of the kubeconfig context to use
.PP
\fB\-\-insecure\-skip\-tls\-verify\fP=false
If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.
.PP
\fB\-\-kubeconfig\fP=""
Path to the kubeconfig file to use for CLI requests.
.PP
\fB\-\-log\-backtrace\-at\fP=:0
when logging hits line file:N, emit a stack trace
.PP
\fB\-\-log\-dir\fP=""
If non\-empty, write log files in this directory
.PP
\fB\-\-log\-flush\-frequency\fP=5s
Maximum number of seconds between log flushes
.PP
\fB\-\-logtostderr\fP=true
log to standard error instead of files
.PP
\fB\-\-match\-server\-version\fP=false
Require server version to match client version
.PP
\fB\-\-namespace\fP=""
If present, the namespace scope for this CLI request.
.PP
\fB\-\-password\fP=""
Password for basic authentication to the API server.
.PP
\fB\-s\fP, \fB\-\-server\fP=""
The address and port of the Kubernetes API server
.PP
\fB\-\-stderrthreshold\fP=2
logs at or above this threshold go to stderr
.PP
\fB\-\-token\fP=""
Bearer token for authentication to the API server.
.PP
\fB\-\-user\fP=""
The name of the kubeconfig user to use
.PP
\fB\-\-username\fP=""
Username for basic authentication to the API server.
.PP
\fB\-\-v\fP=0
log level for V logs
.PP
\fB\-\-vmodule\fP=
comma\-separated list of pattern=N settings for file\-filtered logging
.SH EXAMPLE
.PP
.RS
.nf
# 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
.fi
.RE
.SH SEE ALSO
.PP
\fBkubectl\-create(1)\fP,
.SH HISTORY
.PP
January 2015, Originally compiled by Eric Paris (eparis at redhat dot com) based on the kubernetes source material, but hopefully they have been automatically generated since!

View File

@ -13,7 +13,7 @@ kubectl create secret generic \- Create a secret from a local file, directory or
.SH DESCRIPTION
.PP
Create a secret based on a file, directory, or specified literal value
Create a secret based on a file, directory, or specified literal value.
.PP
A single secret may package one or more key/value pairs.

View File

@ -156,7 +156,7 @@ $ cat pod.json | kubectl create \-f \-
.SH SEE ALSO
.PP
\fBkubectl(1)\fP, \fBkubectl\-create\-namespace(1)\fP, \fBkubectl\-create\-secret(1)\fP,
\fBkubectl(1)\fP, \fBkubectl\-create\-namespace(1)\fP, \fBkubectl\-create\-secret(1)\fP, \fBkubectl\-create\-configmap(1)\fP,
.SH HISTORY

View File

@ -99,10 +99,11 @@ $ cat pod.json | kubectl create -f -
### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
* [kubectl create configmap](kubectl_create_configmap.md) - Create a configMap from a local file, directory or literal value.
* [kubectl create namespace](kubectl_create_namespace.md) - Create a namespace with the specified name.
* [kubectl create secret](kubectl_create_secret.md) - Create a secret using specified subcommand.
###### Auto generated by spf13/cobra on 22-Jan-2016
###### Auto generated by spf13/cobra on 17-Feb-2016
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create.md?pixel)]()

View File

@ -0,0 +1,121 @@
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
<!-- BEGIN STRIP_FOR_RELEASE -->
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
width="25" height="25">
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
If you are using a released version of Kubernetes, you should
refer to the docs that go with that version.
Documentation for other releases can be found at
[releases.k8s.io](http://releases.k8s.io).
</strong>
--
<!-- END STRIP_FOR_RELEASE -->
<!-- END MUNGE: UNVERSIONED_WARNING -->
## kubectl create configmap
Create a configMap from a local file, directory or literal value.
### Synopsis
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).
```
kubectl create configmap NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]
```
### Examples
```
# 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
```
### Options
```
--dry-run[=false]: If true, only print the object that would be sent, without sending it.
--from-file=[]: 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.
--from-literal=[]: Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)
--generator="configmap/v1": The name of the API generator to use.
--no-headers[=false]: When using the default output, don't print headers.
-o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].
--output-version="": Output the formatted object with the given version (default api-version).
--save-config[=false]: If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future.
--schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema'
-a, --show-all[=false]: When printing, show all resources (default hide terminated pods.)
--show-labels[=false]: When printing, show all labels as the last column (default hide labels column)
--sort-by="": If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.
--template="": Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
--validate[=true]: If true, use a schema to validate the input before sending it
```
### Options inherited from parent commands
```
--alsologtostderr[=false]: log to standard error as well as files
--api-version="": The API version to use when talking to the server
--certificate-authority="": Path to a cert. file for the certificate authority.
--client-certificate="": Path to a client certificate file for TLS.
--client-key="": Path to a client key file for TLS.
--cluster="": The name of the kubeconfig cluster to use
--context="": The name of the kubeconfig context to use
--insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.
--kubeconfig="": Path to the kubeconfig file to use for CLI requests.
--log-backtrace-at=:0: when logging hits line file:N, emit a stack trace
--log-dir="": If non-empty, write log files in this directory
--log-flush-frequency=5s: Maximum number of seconds between log flushes
--logtostderr[=true]: log to standard error instead of files
--match-server-version[=false]: Require server version to match client version
--namespace="": If present, the namespace scope for this CLI request.
--password="": Password for basic authentication to the API server.
-s, --server="": The address and port of the Kubernetes API server
--stderrthreshold=2: logs at or above this threshold go to stderr
--token="": Bearer token for authentication to the API server.
--user="": The name of the kubeconfig user to use
--username="": Username for basic authentication to the API server.
--v=0: log level for V logs
--vmodule=: comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [kubectl create](kubectl_create.md) - Create a resource by filename or stdin
###### Auto generated by spf13/cobra on 18-Feb-2016
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_configmap.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View File

@ -35,7 +35,7 @@ Create a secret from a local file, directory or literal value.
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.
@ -116,7 +116,7 @@ kubectl create secret generic NAME [--type=string] [--from-file=[key=]source] [-
* [kubectl create secret](kubectl_create_secret.md) - Create a secret using specified subcommand.
###### Auto generated by spf13/cobra on 14-Feb-2016
###### Auto generated by spf13/cobra on 18-Feb-2016
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_secret_generic.md?pixel)]()

View File

@ -823,6 +823,34 @@ __EOF__
# Clean up
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 #
#################
@ -1271,14 +1299,6 @@ __EOF__
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 #
######################

View File

@ -78,6 +78,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
// create subcommands
cmd.AddCommand(NewCmdCreateNamespace(f, out))
cmd.AddCommand(NewCmdCreateSecret(f, out))
cmd.AddCommand(NewCmdCreateConfigMap(f, out))
return cmd
}

View 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"),
})
}

View 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)
}
}

View File

@ -43,7 +43,7 @@ func NewCmdCreateSecret(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
const (
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.
@ -87,7 +87,7 @@ func NewCmdCreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Comm
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 {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {

View File

@ -142,6 +142,7 @@ const (
NamespaceV1GeneratorName = "namespace/v1"
SecretV1GeneratorName = "secret/v1"
SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1"
ConfigMapV1GeneratorName = "configmap/v1"
)
// DefaultGenerators returns the set of default generators for use in Factory instances

212
pkg/kubectl/configmap.go Normal file
View 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
}

View 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))
}
}
}

View File

@ -18,6 +18,9 @@ limitations under the License.
package kubectl
import (
"errors"
"fmt"
"path"
"strings"
"k8s.io/kubernetes/pkg/api"
@ -120,3 +123,36 @@ func expandResourceShortcut(resource unversioned.GroupVersionResource) unversion
}
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
View 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)
}
}
}

View File

@ -17,7 +17,6 @@ limitations under the License.
package kubectl
import (
"errors"
"fmt"
"io/ioutil"
"os"
@ -206,33 +205,3 @@ func addKeyFromLiteralToSecret(secret *api.Secret, keyName string, data []byte)
secret.Data[keyName] = data
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
}