mirror of https://github.com/k3s-io/k3s
Add 'kubectl set image'
parent
28333a0041
commit
4332472bde
|
@ -53,6 +53,7 @@ docs/man/man1/kubectl-rollout-undo.1
|
|||
docs/man/man1/kubectl-rollout.1
|
||||
docs/man/man1/kubectl-run.1
|
||||
docs/man/man1/kubectl-scale.1
|
||||
docs/man/man1/kubectl-set-image.1
|
||||
docs/man/man1/kubectl-set.1
|
||||
docs/man/man1/kubectl-stop.1
|
||||
docs/man/man1/kubectl-taint.1
|
||||
|
@ -109,6 +110,7 @@ docs/user-guide/kubectl/kubectl_rollout_undo.md
|
|||
docs/user-guide/kubectl/kubectl_run.md
|
||||
docs/user-guide/kubectl/kubectl_scale.md
|
||||
docs/user-guide/kubectl/kubectl_set.md
|
||||
docs/user-guide/kubectl/kubectl_set_image.md
|
||||
docs/user-guide/kubectl/kubectl_taint.md
|
||||
docs/user-guide/kubectl/kubectl_uncordon.md
|
||||
docs/user-guide/kubectl/kubectl_version.md
|
||||
|
|
|
@ -478,10 +478,78 @@ _kubectl_get()
|
|||
noun_aliases+=("thirdpartyresources")
|
||||
}
|
||||
|
||||
_kubectl_set_image()
|
||||
{
|
||||
last_command="kubectl_set_image"
|
||||
commands=()
|
||||
|
||||
flags=()
|
||||
two_word_flags=()
|
||||
flags_with_completion=()
|
||||
flags_completion=()
|
||||
|
||||
flags+=("--all")
|
||||
flags+=("--filename=")
|
||||
flags_with_completion+=("--filename")
|
||||
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
|
||||
two_word_flags+=("-f")
|
||||
flags_with_completion+=("-f")
|
||||
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
|
||||
flags+=("--local")
|
||||
flags+=("--no-headers")
|
||||
flags+=("--output=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--output-version=")
|
||||
flags+=("--record")
|
||||
flags+=("--recursive")
|
||||
flags+=("-R")
|
||||
flags+=("--selector=")
|
||||
two_word_flags+=("-l")
|
||||
flags+=("--show-all")
|
||||
flags+=("-a")
|
||||
flags+=("--show-labels")
|
||||
flags+=("--sort-by=")
|
||||
flags+=("--template=")
|
||||
flags_with_completion+=("--template")
|
||||
flags_completion+=("_filedir")
|
||||
flags+=("--alsologtostderr")
|
||||
flags+=("--api-version=")
|
||||
flags+=("--as=")
|
||||
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_with_completion+=("--namespace")
|
||||
flags_completion+=("__kubectl_get_namespaces")
|
||||
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=()
|
||||
noun_aliases=()
|
||||
}
|
||||
|
||||
_kubectl_set()
|
||||
{
|
||||
last_command="kubectl_set"
|
||||
commands=()
|
||||
commands+=("image")
|
||||
|
||||
flags=()
|
||||
two_word_flags=()
|
||||
|
|
|
@ -27,7 +27,7 @@ An autoscaler can automatically increase or decrease number of pods deployed wit
|
|||
|
||||
.PP
|
||||
\fB\-\-dry\-run\fP=false
|
||||
If true, only print the object that would be sent, without creating it.
|
||||
If true, only print the object that would be sent, without sending it.
|
||||
|
||||
.PP
|
||||
\fB\-f\fP, \fB\-\-filename\fP=[]
|
||||
|
|
|
@ -40,7 +40,7 @@ Possible resources include (case insensitive):
|
|||
|
||||
.PP
|
||||
\fB\-\-dry\-run\fP=false
|
||||
If true, only print the object that would be sent, without creating it.
|
||||
If true, only print the object that would be sent, without sending it.
|
||||
|
||||
.PP
|
||||
\fB\-\-external\-ip\fP=""
|
||||
|
|
|
@ -32,7 +32,7 @@ existing replication controller and overwrite at least one (common) label in its
|
|||
|
||||
.PP
|
||||
\fB\-\-dry\-run\fP=false
|
||||
If true, print out the changes that would be made, but don't actually make them.
|
||||
If true, only print the object that would be sent, without sending it.
|
||||
|
||||
.PP
|
||||
\fB\-f\fP, \fB\-\-filename\fP=[]
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" ""
|
||||
|
||||
|
||||
.SH NAME
|
||||
.PP
|
||||
kubectl set image \- Update image of a pod template
|
||||
|
||||
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBkubectl set image\fP [OPTIONS]
|
||||
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Update existing container image(s) of resources.
|
||||
|
||||
.PP
|
||||
Possible resources include (case insensitive):
|
||||
pod (po), replicationcontroller (rc), deployment, daemonset (ds), job, replicaset (rs)
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\-\-all\fP=false
|
||||
select all resources in the namespace of the specified resource types
|
||||
|
||||
.PP
|
||||
\fB\-f\fP, \fB\-\-filename\fP=[]
|
||||
Filename, directory, or URL to a file identifying the resource to get from a server.
|
||||
|
||||
.PP
|
||||
\fB\-\-local\fP=false
|
||||
If true, set image will NOT contact api\-server but run locally.
|
||||
|
||||
.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 group version (for ex: 'extensions/v1beta1').
|
||||
|
||||
.PP
|
||||
\fB\-\-record\fP=false
|
||||
Record current kubectl command in the resource annotation.
|
||||
|
||||
.PP
|
||||
\fB\-R\fP, \fB\-\-recursive\fP=false
|
||||
If true, process directory recursively.
|
||||
|
||||
.PP
|
||||
\fB\-l\fP, \fB\-\-selector\fP=""
|
||||
Selector (label query) to filter on
|
||||
|
||||
.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]].
|
||||
|
||||
|
||||
.SH OPTIONS INHERITED FROM PARENT COMMANDS
|
||||
.PP
|
||||
\fB\-\-alsologtostderr\fP=false
|
||||
log to standard error as well as files
|
||||
|
||||
.PP
|
||||
\fB\-\-api\-version\fP=""
|
||||
DEPRECATED: The API version to use when talking to the server
|
||||
|
||||
.PP
|
||||
\fB\-\-as\fP=""
|
||||
Username to impersonate for the operation.
|
||||
|
||||
.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
|
||||
# Set a deployment's nginx container image to 'nginx:1.9.1', and its busybox container image to 'busybox'.
|
||||
kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
|
||||
|
||||
# Update all deployments' and rc's nginx container's image to 'nginx:1.9.1'
|
||||
kubectl set image deployments,rc nginx=nginx:1.9.1 \-\-all
|
||||
|
||||
# Update image of all containers of daemonset abc to 'nginx:1.9.1'
|
||||
kubectl set image daemonset abc *=nginx:1.9.1
|
||||
|
||||
# Print result (in yaml format) of updating nginx container image from local file, without hitting the server
|
||||
kubectl set image \-f path/to/file.yaml nginx=nginx:1.9.1 \-\-local \-o yaml
|
||||
|
||||
.fi
|
||||
.RE
|
||||
|
||||
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\fBkubectl\-set(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!
|
|
@ -119,7 +119,7 @@ These commands help you make changes to existing application resources.
|
|||
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\fBkubectl(1)\fP,
|
||||
\fBkubectl(1)\fP, \fBkubectl\-set\-image(1)\fP,
|
||||
|
||||
|
||||
.SH HISTORY
|
||||
|
|
|
@ -62,7 +62,7 @@ kubectl autoscale rc foo --max=5 --cpu-percent=80
|
|||
|
||||
```
|
||||
--cpu-percent=-1: The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, the server will apply a default value.
|
||||
--dry-run[=false]: If true, only print the object that would be sent, without creating it.
|
||||
--dry-run[=false]: If true, only print the object that would be sent, without sending it.
|
||||
-f, --filename=[]: Filename, directory, or URL to a file identifying the resource to autoscale.
|
||||
--generator="horizontalpodautoscaler/v1beta1": The name of the API generator to use. Currently there is only 1 generator.
|
||||
--include-extended-apis[=true]: If true, include definitions of new APIs via calls to the API server. [default true]
|
||||
|
@ -113,7 +113,7 @@ kubectl autoscale rc foo --max=5 --cpu-percent=80
|
|||
|
||||
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
|
||||
|
||||
###### Auto generated by spf13/cobra on 30-Mar-2016
|
||||
###### Auto generated by spf13/cobra on 13-May-2016
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_autoscale.md?pixel)]()
|
||||
|
|
|
@ -85,7 +85,7 @@ kubectl expose deployment nginx --port=80 --target-port=8000
|
|||
### Options
|
||||
|
||||
```
|
||||
--dry-run[=false]: If true, only print the object that would be sent, without creating it.
|
||||
--dry-run[=false]: If true, only print the object that would be sent, without sending it.
|
||||
--external-ip="": Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP.
|
||||
-f, --filename=[]: Filename, directory, or URL to a file identifying the resource to expose a service
|
||||
--generator="service/v2": The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.
|
||||
|
@ -143,7 +143,7 @@ kubectl expose deployment nginx --port=80 --target-port=8000
|
|||
|
||||
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
|
||||
|
||||
###### Auto generated by spf13/cobra on 11-May-2016
|
||||
###### Auto generated by spf13/cobra on 13-May-2016
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_expose.md?pixel)]()
|
||||
|
|
|
@ -75,7 +75,7 @@ kubectl rolling-update frontend-v1 frontend-v2 --rollback
|
|||
```
|
||||
--container="": Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod
|
||||
--deployment-label-key="deployment": The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise
|
||||
--dry-run[=false]: If true, print out the changes that would be made, but don't actually make them.
|
||||
--dry-run[=false]: If true, only print the object that would be sent, without sending it.
|
||||
-f, --filename=[]: Filename or URL to file to use to create the new replication controller.
|
||||
--image="": Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f
|
||||
--image-pull-policy="": Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.
|
||||
|
@ -127,7 +127,7 @@ kubectl rolling-update frontend-v1 frontend-v2 --rollback
|
|||
|
||||
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Apr-2016
|
||||
###### Auto generated by spf13/cobra on 13-May-2016
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_rolling-update.md?pixel)]()
|
||||
|
|
|
@ -73,6 +73,7 @@ kubectl set SUBCOMMAND
|
|||
### SEE ALSO
|
||||
|
||||
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
|
||||
* [kubectl set image](kubectl_set_image.md) - Update image of a pod template
|
||||
|
||||
###### Auto generated by spf13/cobra on 10-May-2016
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
<!-- 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 set image
|
||||
|
||||
Update image of a pod template
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
Update existing container image(s) of resources.
|
||||
|
||||
Possible resources include (case insensitive):
|
||||
pod (po), replicationcontroller (rc), deployment, daemonset (ds), job, replicaset (rs)
|
||||
|
||||
```
|
||||
kubectl set image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
# Set a deployment's nginx container image to 'nginx:1.9.1', and its busybox container image to 'busybox'.
|
||||
kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
|
||||
|
||||
# Update all deployments' and rc's nginx container's image to 'nginx:1.9.1'
|
||||
kubectl set image deployments,rc nginx=nginx:1.9.1 --all
|
||||
|
||||
# Update image of all containers of daemonset abc to 'nginx:1.9.1'
|
||||
kubectl set image daemonset abc *=nginx:1.9.1
|
||||
|
||||
# Print result (in yaml format) of updating nginx container image from local file, without hitting the server
|
||||
kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--all[=false]: select all resources in the namespace of the specified resource types
|
||||
-f, --filename=[]: Filename, directory, or URL to a file identifying the resource to get from a server.
|
||||
--local[=false]: If true, set image will NOT contact api-server but run locally.
|
||||
--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 group version (for ex: 'extensions/v1beta1').
|
||||
--record[=false]: Record current kubectl command in the resource annotation.
|
||||
-R, --recursive[=false]: If true, process directory recursively.
|
||||
-l, --selector="": Selector (label query) to filter on
|
||||
-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].
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--alsologtostderr[=false]: log to standard error as well as files
|
||||
--as="": Username to impersonate for the operation.
|
||||
--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 set](kubectl_set.md) - Set specific features on objects
|
||||
|
||||
###### Auto generated by spf13/cobra on 17-May-2016
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_set_image.md?pixel)]()
|
||||
<!-- END MUNGE: GENERATED_ANALYTICS -->
|
|
@ -14,7 +14,7 @@ options:
|
|||
- name: dry-run
|
||||
default_value: "false"
|
||||
usage: |
|
||||
If true, only print the object that would be sent, without creating it.
|
||||
If true, only print the object that would be sent, without sending it.
|
||||
- name: filename
|
||||
shorthand: f
|
||||
default_value: '[]'
|
||||
|
|
|
@ -21,7 +21,7 @@ options:
|
|||
- name: dry-run
|
||||
default_value: "false"
|
||||
usage: |
|
||||
If true, only print the object that would be sent, without creating it.
|
||||
If true, only print the object that would be sent, without sending it.
|
||||
- name: external-ip
|
||||
usage: |
|
||||
Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP.
|
||||
|
|
|
@ -17,7 +17,7 @@ options:
|
|||
- name: dry-run
|
||||
default_value: "false"
|
||||
usage: |
|
||||
If true, print out the changes that would be made, but don't actually make them.
|
||||
If true, only print the object that would be sent, without sending it.
|
||||
- name: filename
|
||||
shorthand: f
|
||||
default_value: '[]'
|
||||
|
|
|
@ -65,3 +65,4 @@ inherited_options:
|
|||
comma-separated list of pattern=N settings for file-filtered logging
|
||||
see_also:
|
||||
- kubectl
|
||||
- image
|
||||
|
|
|
@ -291,6 +291,7 @@ runTests() {
|
|||
secret_data=".data"
|
||||
secret_type=".type"
|
||||
deployment_image_field="(index .spec.template.spec.containers 0).image"
|
||||
deployment_second_image_field="(index .spec.template.spec.containers 1).image"
|
||||
change_cause_annotation='.*kubernetes.io/change-cause.*'
|
||||
|
||||
# Passing no arguments to create is an error
|
||||
|
@ -1620,6 +1621,11 @@ __EOF__
|
|||
# Clean up
|
||||
kubectl delete rc frontend "${kube_flags[@]}"
|
||||
|
||||
|
||||
######################
|
||||
# Deployments #
|
||||
######################
|
||||
|
||||
### Auto scale deployment
|
||||
# Pre-condition: no deployment exists
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||
|
@ -1670,6 +1676,39 @@ __EOF__
|
|||
# Clean up
|
||||
kubectl delete deployment nginx-deployment "${kube_flags[@]}"
|
||||
|
||||
### Set image of a deployment
|
||||
# Pre-condition: no deployment exists
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" ''
|
||||
# Create a deployment
|
||||
kubectl create -f hack/testdata/deployment-multicontainer.yaml "${kube_flags[@]}"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx-deployment:'
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_second_image_field}}:{{end}}" "${IMAGE_PERL}:"
|
||||
# Set the deployment's image
|
||||
kubectl set image deployment nginx-deployment nginx="${IMAGE_DEPLOYMENT_R2}" "${kube_flags[@]}"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_second_image_field}}:{{end}}" "${IMAGE_PERL}:"
|
||||
# Set non-existing container should fail
|
||||
! kubectl set image deployment nginx-deployment redis=redis "${kube_flags[@]}"
|
||||
# Set image of deployments without specifying name
|
||||
kubectl set image deployments --all nginx="${IMAGE_DEPLOYMENT_R1}" "${kube_flags[@]}"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_second_image_field}}:{{end}}" "${IMAGE_PERL}:"
|
||||
# Set image of a deployment specified by file
|
||||
kubectl set image -f hack/testdata/deployment-multicontainer.yaml nginx="${IMAGE_DEPLOYMENT_R2}" "${kube_flags[@]}"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_second_image_field}}:{{end}}" "${IMAGE_PERL}:"
|
||||
# Set image of a local file without talking to the server
|
||||
kubectl set image -f hack/testdata/deployment-multicontainer.yaml nginx="${IMAGE_DEPLOYMENT_R1}" "${kube_flags[@]}" --local -o yaml
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_second_image_field}}:{{end}}" "${IMAGE_PERL}:"
|
||||
# Set image of all containers of the deployment
|
||||
kubectl set image deployment nginx-deployment "*"="${IMAGE_DEPLOYMENT_R1}" "${kube_flags[@]}"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
|
||||
kube::test::get_object_assert deployment "{{range.items}}{{$deployment_second_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R1}:"
|
||||
# Clean up
|
||||
kubectl delete deployment nginx-deployment "${kube_flags[@]}"
|
||||
|
||||
|
||||
######################
|
||||
# Replica Sets #
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
labels:
|
||||
name: nginx-deployment
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
name: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: gcr.io/google-containers/nginx:test-cmd
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- name: perl
|
||||
image: gcr.io/google-containers/perl
|
|
@ -148,22 +148,11 @@ func (o *AnnotateOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra
|
|||
|
||||
// retrieves resource and annotation args from args
|
||||
// also checks args to verify that all resources are specified before annotations
|
||||
annotationArgs := []string{}
|
||||
metAnnotaionArg := false
|
||||
for _, s := range args {
|
||||
isAnnotation := strings.Contains(s, "=") || strings.HasSuffix(s, "-")
|
||||
switch {
|
||||
case !metAnnotaionArg && isAnnotation:
|
||||
metAnnotaionArg = true
|
||||
fallthrough
|
||||
case metAnnotaionArg && isAnnotation:
|
||||
annotationArgs = append(annotationArgs, s)
|
||||
case !metAnnotaionArg && !isAnnotation:
|
||||
o.resources = append(o.resources, s)
|
||||
case metAnnotaionArg && !isAnnotation:
|
||||
return fmt.Errorf("all resources must be specified before annotation changes: %s", s)
|
||||
}
|
||||
resources, annotationArgs, err := cmdutil.GetResourcesAndPairs(args, "annotation")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.resources = resources
|
||||
if len(o.resources) < 1 && len(o.filenames) == 0 {
|
||||
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
|
||||
}
|
||||
|
@ -277,34 +266,7 @@ func (o AnnotateOptions) RunAnnotate() error {
|
|||
|
||||
// parseAnnotations retrieves new and remove annotations from annotation args
|
||||
func parseAnnotations(annotationArgs []string) (map[string]string, []string, error) {
|
||||
var invalidBuf bytes.Buffer
|
||||
newAnnotations := map[string]string{}
|
||||
removeAnnotations := []string{}
|
||||
for _, annotationArg := range annotationArgs {
|
||||
if strings.Index(annotationArg, "=") != -1 {
|
||||
parts := strings.SplitN(annotationArg, "=", 2)
|
||||
if len(parts) != 2 || len(parts[1]) == 0 {
|
||||
if invalidBuf.Len() > 0 {
|
||||
invalidBuf.WriteString(", ")
|
||||
}
|
||||
invalidBuf.WriteString(fmt.Sprintf(annotationArg))
|
||||
} else {
|
||||
newAnnotations[parts[0]] = parts[1]
|
||||
}
|
||||
} else if strings.HasSuffix(annotationArg, "-") {
|
||||
removeAnnotations = append(removeAnnotations, annotationArg[:len(annotationArg)-1])
|
||||
} else {
|
||||
if invalidBuf.Len() > 0 {
|
||||
invalidBuf.WriteString(", ")
|
||||
}
|
||||
invalidBuf.WriteString(fmt.Sprintf(annotationArg))
|
||||
}
|
||||
}
|
||||
if invalidBuf.Len() > 0 {
|
||||
return newAnnotations, removeAnnotations, fmt.Errorf("invalid annotation format: %s", invalidBuf.String())
|
||||
}
|
||||
|
||||
return newAnnotations, removeAnnotations, nil
|
||||
return cmdutil.ParsePairs(annotationArgs, "annotation", true)
|
||||
}
|
||||
|
||||
// validateAnnotations checks the format of annotation args and checks removed annotations aren't in the new annotations map
|
||||
|
|
|
@ -68,7 +68,7 @@ func NewCmdAutoscale(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||
cmd.MarkFlagRequired("max")
|
||||
cmd.Flags().Int("cpu-percent", -1, fmt.Sprintf("The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, the server will apply a default value."))
|
||||
cmd.Flags().String("name", "", "The name for the newly created object. If not specified, the name of the input resource will be used.")
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without creating it.")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
usage := "Filename, directory, or URL to a file identifying the resource to autoscale."
|
||||
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
|
||||
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
|
||||
|
@ -160,8 +160,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
|
|||
}
|
||||
object = hpa.Object
|
||||
}
|
||||
// TODO: extract this flag to a central location, when such a location exists.
|
||||
if cmdutil.GetFlagBool(cmd, "dry-run") {
|
||||
if cmdutil.GetDryRunFlag(cmd) {
|
||||
return f.PrintObject(cmd, mapper, object, out)
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ func CreateConfigMap(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, a
|
|||
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
|
||||
Name: name,
|
||||
StructuredGenerator: generator,
|
||||
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
|
||||
DryRun: cmdutil.GetDryRunFlag(cmd),
|
||||
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func CreateNamespace(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, a
|
|||
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
|
||||
Name: name,
|
||||
StructuredGenerator: generator,
|
||||
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
|
||||
DryRun: cmdutil.GetDryRunFlag(cmd),
|
||||
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ func CreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comman
|
|||
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
|
||||
Name: name,
|
||||
StructuredGenerator: generator,
|
||||
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
|
||||
DryRun: cmdutil.GetDryRunFlag(cmd),
|
||||
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||
})
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ func CreateSecretDockerRegistry(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra
|
|||
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
|
||||
Name: name,
|
||||
StructuredGenerator: generator,
|
||||
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
|
||||
DryRun: cmdutil.GetDryRunFlag(cmd),
|
||||
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ func CreateServiceAccount(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Comma
|
|||
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
|
||||
Name: name,
|
||||
StructuredGenerator: generator,
|
||||
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
|
||||
DryRun: cmdutil.GetDryRunFlag(cmd),
|
||||
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -110,7 +110,6 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||
cmd.Flags().String("load-balancer-ip", "", "IP to assign to to the Load Balancer. If empty, an ephemeral IP will be created and used (cloud-provider specific).")
|
||||
cmd.Flags().String("selector", "", "A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.")
|
||||
cmd.Flags().StringP("labels", "l", "", "Labels to apply to the service created by this call.")
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without creating it.")
|
||||
cmd.Flags().String("container-port", "", "Synonym for --target-port")
|
||||
cmd.Flags().MarkDeprecated("container-port", "--container-port will be removed in the future, please use --target-port instead")
|
||||
cmd.Flags().String("target-port", "", "Name or number for the port on the container that the service should direct traffic to. Optional.")
|
||||
|
@ -121,6 +120,7 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||
|
||||
usage := "Filename, directory, or URL to a file identifying the resource to expose a service"
|
||||
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
cmdutil.AddRecordFlag(cmd)
|
||||
|
@ -256,8 +256,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
|
|||
}
|
||||
}
|
||||
info.Refresh(object, true)
|
||||
// TODO: extract this flag to a central location, when such a location exists.
|
||||
if cmdutil.GetFlagBool(cmd, "dry-run") {
|
||||
if cmdutil.GetDryRunFlag(cmd) {
|
||||
return f.PrintObject(cmd, mapper, object, out)
|
||||
}
|
||||
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil {
|
||||
|
|
|
@ -101,7 +101,7 @@ func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||
usage := "Filename, directory, or URL to a file identifying the resource to update the labels"
|
||||
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
|
||||
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddRecordFlag(cmd)
|
||||
cmdutil.AddInclude3rdPartyFlags(cmd)
|
||||
|
||||
|
@ -176,21 +176,9 @@ func labelFunc(obj runtime.Object, overwrite bool, resourceVersion string, label
|
|||
}
|
||||
|
||||
func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *LabelOptions) error {
|
||||
resources, labelArgs := []string{}, []string{}
|
||||
first := true
|
||||
for _, s := range args {
|
||||
isLabel := strings.Contains(s, "=") || strings.HasSuffix(s, "-")
|
||||
switch {
|
||||
case first && isLabel:
|
||||
first = false
|
||||
fallthrough
|
||||
case !first && isLabel:
|
||||
labelArgs = append(labelArgs, s)
|
||||
case first && !isLabel:
|
||||
resources = append(resources, s)
|
||||
case !first && !isLabel:
|
||||
return cmdutil.UsageError(cmd, "all resources must be specified before label changes: %s", s)
|
||||
}
|
||||
resources, labelArgs, err := cmdutil.GetResourcesAndPairs(args, "label")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resources) < 1 && len(options.Filenames) == 0 {
|
||||
return cmdutil.UsageError(cmd, "one or more resources must be specified as <resource> <name> or <resource>/<name>")
|
||||
|
@ -242,7 +230,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
|
|||
|
||||
var outputObj runtime.Object
|
||||
dataChangeMsg := "not labeled"
|
||||
if cmdutil.GetFlagBool(cmd, "dry-run") {
|
||||
if cmdutil.GetDryRunFlag(cmd) {
|
||||
err = labelFunc(info.Object, overwrite, resourceVersion, lbls, remove)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -98,8 +98,8 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||
cmd.Flags().String("deployment-label-key", "deployment", "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise")
|
||||
cmd.Flags().String("container", "", "Container name which will have its image upgraded. Only relevant when --image is specified, ignored otherwise. Required when using --image on a multi-container pod")
|
||||
cmd.Flags().String("image-pull-policy", "", "Explicit policy for when to pull container images. Required when --image is same as existing image, ignored otherwise.")
|
||||
cmd.Flags().Bool("dry-run", false, "If true, print out the changes that would be made, but don't actually make them.")
|
||||
cmd.Flags().Bool("rollback", false, "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddValidateFlags(cmd)
|
||||
cmdutil.AddPrinterFlags(cmd)
|
||||
cmdutil.AddInclude3rdPartyFlags(cmd)
|
||||
|
@ -157,7 +157,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
|
|||
period := cmdutil.GetFlagDuration(cmd, "update-period")
|
||||
interval := cmdutil.GetFlagDuration(cmd, "poll-interval")
|
||||
timeout := cmdutil.GetFlagDuration(cmd, "timeout")
|
||||
dryrun := cmdutil.GetFlagBool(cmd, "dry-run")
|
||||
dryrun := cmdutil.GetDryRunFlag(cmd)
|
||||
outputFormat := cmdutil.GetFlagString(cmd, "output")
|
||||
container := cmdutil.GetFlagString(cmd, "container")
|
||||
|
||||
|
|
|
@ -92,12 +92,12 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
|
|||
}
|
||||
|
||||
func addRunFlags(cmd *cobra.Command) {
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmd.Flags().String("generator", "", "The name of the API generator to use. Default is 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/v1'. This will happen only for cluster version at least 1.2, for olders we will fallback to 'run/v1' for --restart=Always, 'run-pod/v1' for others.")
|
||||
cmd.Flags().String("image", "", "The image for the container to run.")
|
||||
cmd.MarkFlagRequired("image")
|
||||
cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.")
|
||||
cmd.Flags().Bool("rm", false, "If true, delete resources created in this command for attached containers.")
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
|
||||
cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.")
|
||||
cmd.Flags().StringSlice("env", []string{}, "Environment variables to set in the container")
|
||||
cmd.Flags().Int("port", -1, "The port that this container exposes. If --expose is true, this is also the port used by the service that is created.")
|
||||
|
@ -474,8 +474,7 @@ func createGeneratedObject(f *cmdutil.Factory, cmd *cobra.Command, generator kub
|
|||
return nil, "", nil, nil, err
|
||||
}
|
||||
}
|
||||
// TODO: extract this flag to a central location, when such a location exists.
|
||||
if !cmdutil.GetFlagBool(cmd, "dry-run") {
|
||||
if !cmdutil.GetDryRunFlag(cmd) {
|
||||
resourceMapper := &resource.Mapper{
|
||||
ObjectTyper: typer,
|
||||
RESTMapper: mapper,
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
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 set
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/strategicpatch"
|
||||
)
|
||||
|
||||
// selectContainers allows one or more containers to be matched against a string or wildcard
|
||||
func selectContainers(containers []api.Container, spec string) ([]*api.Container, []*api.Container) {
|
||||
out := []*api.Container{}
|
||||
skipped := []*api.Container{}
|
||||
for i, c := range containers {
|
||||
if selectString(c.Name, spec) {
|
||||
out = append(out, &containers[i])
|
||||
} else {
|
||||
skipped = append(skipped, &containers[i])
|
||||
}
|
||||
}
|
||||
return out, skipped
|
||||
}
|
||||
|
||||
// handlePodUpdateError prints a more useful error to the end user when mutating a pod.
|
||||
func handlePodUpdateError(out io.Writer, err error, resource string) {
|
||||
if statusError, ok := err.(*errors.StatusError); ok && errors.IsInvalid(err) {
|
||||
errorDetails := statusError.Status().Details
|
||||
if errorDetails.Kind == "Pod" {
|
||||
all, match := true, false
|
||||
for _, cause := range errorDetails.Causes {
|
||||
if cause.Field == "spec" && strings.Contains(cause.Message, "may not update fields other than") {
|
||||
fmt.Fprintf(out, "error: may not update %s in pod %q directly\n", resource, errorDetails.Name)
|
||||
match = true
|
||||
} else {
|
||||
all = false
|
||||
}
|
||||
}
|
||||
if all && match {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "error: %v\n", err)
|
||||
}
|
||||
|
||||
// selectString returns true if the provided string matches spec, where spec is a string with
|
||||
// a non-greedy '*' wildcard operator.
|
||||
// TODO: turn into a regex and handle greedy matches and backtracking.
|
||||
func selectString(s, spec string) bool {
|
||||
if spec == "*" {
|
||||
return true
|
||||
}
|
||||
if !strings.Contains(spec, "*") {
|
||||
return s == spec
|
||||
}
|
||||
|
||||
pos := 0
|
||||
match := true
|
||||
parts := strings.Split(spec, "*")
|
||||
for i, part := range parts {
|
||||
if len(part) == 0 {
|
||||
continue
|
||||
}
|
||||
next := strings.Index(s[pos:], part)
|
||||
switch {
|
||||
// next part not in string
|
||||
case next < pos:
|
||||
fallthrough
|
||||
// first part does not match start of string
|
||||
case i == 0 && pos != 0:
|
||||
fallthrough
|
||||
// last part does not exactly match remaining part of string
|
||||
case i == (len(parts)-1) && len(s) != (len(part)+next):
|
||||
match = false
|
||||
break
|
||||
default:
|
||||
pos = next
|
||||
}
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
// Patch represents the result of a mutation to an object.
|
||||
type Patch struct {
|
||||
Info *resource.Info
|
||||
Err error
|
||||
|
||||
Before []byte
|
||||
After []byte
|
||||
Patch []byte
|
||||
}
|
||||
|
||||
// CalculatePatches calls the mutation function on each provided info object, and generates a strategic merge patch for
|
||||
// the changes in the object. Encoder must be able to encode the info into the appropriate destination type. If mutateFn
|
||||
// returns false, the object is not included in the final list of patches.
|
||||
func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn func(*resource.Info) (bool, error)) []*Patch {
|
||||
var patches []*Patch
|
||||
for _, info := range infos {
|
||||
patch := &Patch{Info: info}
|
||||
patch.Before, patch.Err = runtime.Encode(encoder, info.Object)
|
||||
|
||||
ok, err := mutateFn(info)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
patch.Err = err
|
||||
}
|
||||
patches = append(patches, patch)
|
||||
if patch.Err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
patch.After, patch.Err = runtime.Encode(encoder, info.Object)
|
||||
if patch.Err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: should be via New
|
||||
versioned, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
|
||||
if err != nil {
|
||||
patch.Err = err
|
||||
continue
|
||||
}
|
||||
|
||||
patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, versioned)
|
||||
}
|
||||
return patches
|
||||
}
|
|
@ -42,7 +42,8 @@ func NewCmdSet(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
|||
},
|
||||
}
|
||||
|
||||
// TODO: add subcommands
|
||||
// add subcommands
|
||||
cmd.AddCommand(NewCmdImage(f, out))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
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 set
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/meta"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
utilerrors "k8s.io/kubernetes/pkg/util/errors"
|
||||
)
|
||||
|
||||
// ImageOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
||||
// referencing the cmd.Flags()
|
||||
type ImageOptions struct {
|
||||
Mapper meta.RESTMapper
|
||||
Typer runtime.ObjectTyper
|
||||
Infos []*resource.Info
|
||||
Encoder runtime.Encoder
|
||||
Selector string
|
||||
Out io.Writer
|
||||
Err io.Writer
|
||||
Filenames []string
|
||||
Recursive bool
|
||||
ShortOutput bool
|
||||
All bool
|
||||
Record bool
|
||||
ChangeCause string
|
||||
Local bool
|
||||
Cmd *cobra.Command
|
||||
|
||||
PrintObject func(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error
|
||||
UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error)
|
||||
Resources []string
|
||||
ContainerImages map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
image_resources = `
|
||||
pod (po), replicationcontroller (rc), deployment, daemonset (ds), job, replicaset (rs)`
|
||||
|
||||
image_long = `Update existing container image(s) of resources.
|
||||
|
||||
Possible resources include (case insensitive):` + image_resources
|
||||
|
||||
image_example = `# Set a deployment's nginx container image to 'nginx:1.9.1', and its busybox container image to 'busybox'.
|
||||
kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1
|
||||
|
||||
# Update all deployments' and rc's nginx container's image to 'nginx:1.9.1'
|
||||
kubectl set image deployments,rc nginx=nginx:1.9.1 --all
|
||||
|
||||
# Update image of all containers of daemonset abc to 'nginx:1.9.1'
|
||||
kubectl set image daemonset abc *=nginx:1.9.1
|
||||
|
||||
# Print result (in yaml format) of updating nginx container image from local file, without hitting the server
|
||||
kubectl set image -f path/to/file.yaml nginx=nginx:1.9.1 --local -o yaml`
|
||||
)
|
||||
|
||||
func NewCmdImage(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
||||
options := &ImageOptions{
|
||||
Out: out,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "image (-f FILENAME | TYPE NAME) CONTAINER_NAME_1=CONTAINER_IMAGE_1 ... CONTAINER_NAME_N=CONTAINER_IMAGE_N",
|
||||
Short: "Update image of a pod template",
|
||||
Long: image_long,
|
||||
Example: image_example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(options.Complete(f, cmd, args))
|
||||
cmdutil.CheckErr(options.Validate())
|
||||
cmdutil.CheckErr(options.Run())
|
||||
},
|
||||
}
|
||||
|
||||
cmdutil.AddPrinterFlags(cmd)
|
||||
usage := "Filename, directory, or URL to a file identifying the resource to get from a server."
|
||||
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
|
||||
cmd.Flags().BoolVar(&options.All, "all", false, "select all resources in the namespace of the specified resource types")
|
||||
cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on")
|
||||
cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set image will NOT contact api-server but run locally.")
|
||||
cmdutil.AddRecordFlag(cmd)
|
||||
cmdutil.AddRecursiveFlag(cmd, &options.Recursive)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (o *ImageOptions) Complete(f *cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
o.Mapper, o.Typer = f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
|
||||
o.UpdatePodSpecForObject = f.UpdatePodSpecForObject
|
||||
o.Encoder = f.JSONEncoder()
|
||||
o.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
|
||||
o.Record = cmdutil.GetRecordFlag(cmd)
|
||||
o.ChangeCause = f.Command()
|
||||
o.PrintObject = f.PrintObject
|
||||
o.Cmd = cmd
|
||||
|
||||
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Resources, o.ContainerImages, err = getResourcesAndImages(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
builder := resource.NewBuilder(o.Mapper, o.Typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
|
||||
ContinueOnError().
|
||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
FilenameParam(enforceNamespace, o.Recursive, o.Filenames...).
|
||||
Flatten()
|
||||
if !o.Local {
|
||||
builder = builder.
|
||||
SelectorParam(o.Selector).
|
||||
ResourceTypeOrNameArgs(o.All, o.Resources...).
|
||||
Latest()
|
||||
}
|
||||
o.Infos, err = builder.Do().Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImageOptions) Validate() error {
|
||||
if len(o.Resources) < 1 && len(o.Filenames) == 0 {
|
||||
return fmt.Errorf("one or more resources must be specified as <resource> <name> or <resource>/<name>")
|
||||
}
|
||||
if len(o.ContainerImages) < 1 {
|
||||
return fmt.Errorf("at least one image update is required")
|
||||
} else if len(o.ContainerImages) > 1 && hasWildcardKey(o.ContainerImages) {
|
||||
return fmt.Errorf("all containers are already specified by *, but saw more than one container_name=container_image pairs")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *ImageOptions) Run() error {
|
||||
allErrs := []error{}
|
||||
|
||||
patches := CalculatePatches(o.Infos, o.Encoder, func(info *resource.Info) (bool, error) {
|
||||
transformed := false
|
||||
_, err := o.UpdatePodSpecForObject(info.Object, func(spec *api.PodSpec) error {
|
||||
for name, image := range o.ContainerImages {
|
||||
containerFound := false
|
||||
// Find the container to update, and update its image
|
||||
for i, c := range spec.Containers {
|
||||
if c.Name == name || name == "*" {
|
||||
spec.Containers[i].Image = image
|
||||
containerFound = true
|
||||
// Perform updates
|
||||
transformed = true
|
||||
}
|
||||
}
|
||||
// Add a new container if not found
|
||||
if !containerFound {
|
||||
allErrs = append(allErrs, fmt.Errorf("error: unable to find container named %q", name))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return transformed, err
|
||||
})
|
||||
|
||||
for _, patch := range patches {
|
||||
info := patch.Info
|
||||
if patch.Err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err))
|
||||
continue
|
||||
}
|
||||
|
||||
// no changes
|
||||
if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if o.Local {
|
||||
fmt.Fprintln(o.Out, "running in local mode...")
|
||||
return o.PrintObject(o.Cmd, o.Mapper, info.Object, o.Out)
|
||||
}
|
||||
|
||||
// patch the change
|
||||
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch.Patch)
|
||||
if err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("failed to patch image update to pod template: %v\n", err))
|
||||
continue
|
||||
}
|
||||
info.Refresh(obj, true)
|
||||
|
||||
// record this change (for rollout history)
|
||||
if o.Record || cmdutil.ContainsChangeCause(info) {
|
||||
if err := cmdutil.RecordChangeCause(obj, o.ChangeCause); err == nil {
|
||||
if obj, err = resource.NewHelper(info.Client, info.Mapping).Replace(info.Namespace, info.Name, false, obj); err != nil {
|
||||
allErrs = append(allErrs, fmt.Errorf("changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info.Refresh(obj, true)
|
||||
cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, "image updated")
|
||||
}
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
||||
// getResourcesAndImages retrieves resources and container name:images pair from given args
|
||||
func getResourcesAndImages(args []string) (resources []string, containerImages map[string]string, err error) {
|
||||
pairType := "image"
|
||||
resources, imageArgs, err := cmdutil.GetResourcesAndPairs(args, pairType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
containerImages, _, err = cmdutil.ParsePairs(imageArgs, pairType, false)
|
||||
return
|
||||
}
|
||||
|
||||
func hasWildcardKey(containerImages map[string]string) bool {
|
||||
_, ok := containerImages["*"]
|
||||
return ok
|
||||
}
|
|
@ -139,6 +139,9 @@ type Factory struct {
|
|||
CanBeAutoscaled func(kind unversioned.GroupKind) error
|
||||
// AttachablePodForObject returns the pod to which to attach given an object.
|
||||
AttachablePodForObject func(object runtime.Object) (*api.Pod, error)
|
||||
// UpdatePodSpecForObject will call the provided function on the pod spec this object supports,
|
||||
// return false if no pod spec is supported, or return an error.
|
||||
UpdatePodSpecForObject func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error)
|
||||
// EditorEnvs returns a group of environment variables that the edit command
|
||||
// can range over in order to determine if the user has specified an editor
|
||||
// of their choice.
|
||||
|
@ -708,6 +711,31 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
|
|||
return nil, fmt.Errorf("cannot attach to %v: not implemented", gvk)
|
||||
}
|
||||
},
|
||||
// UpdatePodSpecForObject update the pod specification for the provided object
|
||||
UpdatePodSpecForObject: func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) {
|
||||
// TODO: replace with a swagger schema based approach (identify pod template via schema introspection)
|
||||
switch t := obj.(type) {
|
||||
case *api.Pod:
|
||||
return true, fn(&t.Spec)
|
||||
case *api.ReplicationController:
|
||||
if t.Spec.Template == nil {
|
||||
t.Spec.Template = &api.PodTemplateSpec{}
|
||||
}
|
||||
return true, fn(&t.Spec.Template.Spec)
|
||||
case *extensions.Deployment:
|
||||
return true, fn(&t.Spec.Template.Spec)
|
||||
case *extensions.DaemonSet:
|
||||
return true, fn(&t.Spec.Template.Spec)
|
||||
case *extensions.ReplicaSet:
|
||||
return true, fn(&t.Spec.Template.Spec)
|
||||
case *apps.PetSet:
|
||||
return true, fn(&t.Spec.Template.Spec)
|
||||
case *batch.Job:
|
||||
return true, fn(&t.Spec.Template.Spec)
|
||||
default:
|
||||
return false, fmt.Errorf("the object is not a pod or does not have a pod template")
|
||||
}
|
||||
},
|
||||
EditorEnvs: func() []string {
|
||||
return []string{"KUBE_EDITOR", "EDITOR"}
|
||||
},
|
||||
|
|
|
@ -336,6 +336,11 @@ func AddRecursiveFlag(cmd *cobra.Command, value *bool) {
|
|||
cmd.Flags().BoolVarP(value, "recursive", "R", *value, "If true, process directory recursively.")
|
||||
}
|
||||
|
||||
// AddDryRunFlag adds dry-run flag to a command. Usually used by mutations.
|
||||
func AddDryRunFlag(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
|
||||
}
|
||||
|
||||
func AddApplyAnnotationFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool(ApplyAnnotationsFlag, 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.")
|
||||
}
|
||||
|
@ -344,7 +349,7 @@ func AddApplyAnnotationFlags(cmd *cobra.Command) {
|
|||
// TODO: need to take a pass at other generator commands to use this set of flags
|
||||
func AddGeneratorFlags(cmd *cobra.Command, defaultGenerator string) {
|
||||
cmd.Flags().String("generator", defaultGenerator, "The name of the API generator to use.")
|
||||
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
|
||||
AddDryRunFlag(cmd)
|
||||
}
|
||||
|
||||
func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) {
|
||||
|
@ -433,6 +438,10 @@ func GetRecordFlag(cmd *cobra.Command) bool {
|
|||
return GetFlagBool(cmd, "record")
|
||||
}
|
||||
|
||||
func GetDryRunFlag(cmd *cobra.Command) bool {
|
||||
return GetFlagBool(cmd, "dry-run")
|
||||
}
|
||||
|
||||
// RecordChangeCause annotate change-cause to input runtime object.
|
||||
func RecordChangeCause(obj runtime.Object, changeCause string) error {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
|
@ -528,3 +537,60 @@ func GetIncludeThirdPartyAPIs(cmd *cobra.Command) bool {
|
|||
func AddInclude3rdPartyFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
|
||||
}
|
||||
|
||||
// GetResourcesAndPairs retrieves resources and "KEY=VALUE or KEY-" pair args from given args
|
||||
func GetResourcesAndPairs(args []string, pairType string) (resources []string, pairArgs []string, err error) {
|
||||
foundPair := false
|
||||
for _, s := range args {
|
||||
nonResource := strings.Contains(s, "=") || strings.HasSuffix(s, "-")
|
||||
switch {
|
||||
case !foundPair && nonResource:
|
||||
foundPair = true
|
||||
fallthrough
|
||||
case foundPair && nonResource:
|
||||
pairArgs = append(pairArgs, s)
|
||||
case !foundPair && !nonResource:
|
||||
resources = append(resources, s)
|
||||
case foundPair && !nonResource:
|
||||
err = fmt.Errorf("all resources must be specified before %s changes: %s", pairType, s)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParsePairs retrieves new and remove pairs (if supportRemove is true) from "KEY=VALUE or KEY-" pair args
|
||||
func ParsePairs(pairArgs []string, pairType string, supportRemove bool) (newPairs map[string]string, removePairs []string, err error) {
|
||||
newPairs = map[string]string{}
|
||||
if supportRemove {
|
||||
removePairs = []string{}
|
||||
}
|
||||
var invalidBuf bytes.Buffer
|
||||
|
||||
for _, pairArg := range pairArgs {
|
||||
if strings.Index(pairArg, "=") != -1 {
|
||||
parts := strings.SplitN(pairArg, "=", 2)
|
||||
if len(parts) != 2 || len(parts[1]) == 0 {
|
||||
if invalidBuf.Len() > 0 {
|
||||
invalidBuf.WriteString(", ")
|
||||
}
|
||||
invalidBuf.WriteString(fmt.Sprintf(pairArg))
|
||||
} else {
|
||||
newPairs[parts[0]] = parts[1]
|
||||
}
|
||||
} else if supportRemove && strings.HasSuffix(pairArg, "-") {
|
||||
removePairs = append(removePairs, pairArg[:len(pairArg)-1])
|
||||
} else {
|
||||
if invalidBuf.Len() > 0 {
|
||||
invalidBuf.WriteString(", ")
|
||||
}
|
||||
invalidBuf.WriteString(fmt.Sprintf(pairArg))
|
||||
}
|
||||
}
|
||||
if invalidBuf.Len() > 0 {
|
||||
err = fmt.Errorf("invalid %s format: %s", pairType, invalidBuf.String())
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue