mirror of https://github.com/k3s-io/k3s
commit
49d6c86eb7
|
@ -2,6 +2,7 @@
|
||||||
contrib/completions/bash/kubectl
|
contrib/completions/bash/kubectl
|
||||||
docs/man/man1/kubectl-annotate.1
|
docs/man/man1/kubectl-annotate.1
|
||||||
docs/man/man1/kubectl-api-versions.1
|
docs/man/man1/kubectl-api-versions.1
|
||||||
|
docs/man/man1/kubectl-apply.1
|
||||||
docs/man/man1/kubectl-attach.1
|
docs/man/man1/kubectl-attach.1
|
||||||
docs/man/man1/kubectl-cluster-info.1
|
docs/man/man1/kubectl-cluster-info.1
|
||||||
docs/man/man1/kubectl-config-set-cluster.1
|
docs/man/man1/kubectl-config-set-cluster.1
|
||||||
|
@ -36,6 +37,7 @@ docs/man/man1/kubectl.1
|
||||||
docs/user-guide/kubectl/kubectl.md
|
docs/user-guide/kubectl/kubectl.md
|
||||||
docs/user-guide/kubectl/kubectl_annotate.md
|
docs/user-guide/kubectl/kubectl_annotate.md
|
||||||
docs/user-guide/kubectl/kubectl_api-versions.md
|
docs/user-guide/kubectl/kubectl_api-versions.md
|
||||||
|
docs/user-guide/kubectl/kubectl_apply.md
|
||||||
docs/user-guide/kubectl/kubectl_attach.md
|
docs/user-guide/kubectl/kubectl_attach.md
|
||||||
docs/user-guide/kubectl/kubectl_cluster-info.md
|
docs/user-guide/kubectl/kubectl_cluster-info.md
|
||||||
docs/user-guide/kubectl/kubectl_config.md
|
docs/user-guide/kubectl/kubectl_config.md
|
||||||
|
|
|
@ -504,6 +504,33 @@ _kubectl_edit()
|
||||||
must_have_one_noun=()
|
must_have_one_noun=()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_kubectl_apply()
|
||||||
|
{
|
||||||
|
last_command="kubectl_apply"
|
||||||
|
commands=()
|
||||||
|
|
||||||
|
flags=()
|
||||||
|
two_word_flags=()
|
||||||
|
flags_with_completion=()
|
||||||
|
flags_completion=()
|
||||||
|
|
||||||
|
flags+=("--filename=")
|
||||||
|
flags_with_completion+=("--filename")
|
||||||
|
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
|
||||||
|
two_word_flags+=("-f")
|
||||||
|
flags_with_completion+=("-f")
|
||||||
|
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
|
||||||
|
flags+=("--output=")
|
||||||
|
two_word_flags+=("-o")
|
||||||
|
flags+=("--schema-cache-dir=")
|
||||||
|
flags+=("--validate")
|
||||||
|
|
||||||
|
must_have_one_flag=()
|
||||||
|
must_have_one_flag+=("--filename=")
|
||||||
|
must_have_one_flag+=("-f")
|
||||||
|
must_have_one_noun=()
|
||||||
|
}
|
||||||
|
|
||||||
_kubectl_namespace()
|
_kubectl_namespace()
|
||||||
{
|
{
|
||||||
last_command="kubectl_namespace"
|
last_command="kubectl_namespace"
|
||||||
|
@ -1136,6 +1163,7 @@ _kubectl()
|
||||||
commands+=("patch")
|
commands+=("patch")
|
||||||
commands+=("delete")
|
commands+=("delete")
|
||||||
commands+=("edit")
|
commands+=("edit")
|
||||||
|
commands+=("apply")
|
||||||
commands+=("namespace")
|
commands+=("namespace")
|
||||||
commands+=("logs")
|
commands+=("logs")
|
||||||
commands+=("rolling-update")
|
commands+=("rolling-update")
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
kubectl-apply.1
|
||||||
kubectl-annotate.1
|
kubectl-annotate.1
|
||||||
kubectl-api-versions.1
|
kubectl-api-versions.1
|
||||||
kubectl-attach.1
|
kubectl-attach.1
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" ""
|
||||||
|
|
||||||
|
|
||||||
|
.SH NAME
|
||||||
|
.PP
|
||||||
|
kubectl apply \- Apply a configuration to a resource by filename or stdin
|
||||||
|
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.PP
|
||||||
|
\fBkubectl apply\fP [OPTIONS]
|
||||||
|
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.PP
|
||||||
|
Apply a configuration to a resource by filename or stdin.
|
||||||
|
|
||||||
|
.PP
|
||||||
|
JSON and YAML formats are accepted.
|
||||||
|
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
.PP
|
||||||
|
\fB\-f\fP, \fB\-\-filename\fP=[]
|
||||||
|
Filename, directory, or URL to file that contains the configuration to apply
|
||||||
|
|
||||||
|
.PP
|
||||||
|
\fB\-o\fP, \fB\-\-output\fP=""
|
||||||
|
Output mode. Use "\-o name" for shorter output (resource/name).
|
||||||
|
|
||||||
|
.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\-\-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 key 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
|
||||||
|
# Apply the configuration in pod.json to a pod.
|
||||||
|
$ kubectl apply \-f ./pod.json
|
||||||
|
|
||||||
|
# Apply the JSON passed into stdin to a pod.
|
||||||
|
$ cat pod.json | kubectl apply \-f \-
|
||||||
|
|
||||||
|
.fi
|
||||||
|
.RE
|
||||||
|
|
||||||
|
|
||||||
|
.SH SEE ALSO
|
||||||
|
.PP
|
||||||
|
\fBkubectl(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!
|
|
@ -116,7 +116,7 @@ Find more information at
|
||||||
|
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
.PP
|
.PP
|
||||||
\fBkubectl\-get(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-edit(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-attach(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-annotate(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, \fBkubectl\-explain(1)\fP,
|
\fBkubectl\-get(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-edit(1)\fP, \fBkubectl\-apply(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-attach(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-annotate(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, \fBkubectl\-explain(1)\fP,
|
||||||
|
|
||||||
|
|
||||||
.SH HISTORY
|
.SH HISTORY
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
kubectl.md
|
kubectl.md
|
||||||
|
kubectl_apply.md
|
||||||
kubectl_annotate.md
|
kubectl_annotate.md
|
||||||
kubectl_api-versions.md
|
kubectl_api-versions.md
|
||||||
kubectl_attach.md
|
kubectl_attach.md
|
||||||
|
|
|
@ -78,6 +78,7 @@ kubectl
|
||||||
|
|
||||||
* [kubectl annotate](kubectl_annotate.md) - Update the annotations on a resource
|
* [kubectl annotate](kubectl_annotate.md) - Update the annotations on a resource
|
||||||
* [kubectl api-versions](kubectl_api-versions.md) - Print available API versions.
|
* [kubectl api-versions](kubectl_api-versions.md) - Print available API versions.
|
||||||
|
* [kubectl apply](kubectl_apply.md) - Apply a configuration to a resource by filename or stdin
|
||||||
* [kubectl attach](kubectl_attach.md) - Attach to a running container.
|
* [kubectl attach](kubectl_attach.md) - Attach to a running container.
|
||||||
* [kubectl cluster-info](kubectl_cluster-info.md) - Display cluster info
|
* [kubectl cluster-info](kubectl_cluster-info.md) - Display cluster info
|
||||||
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files
|
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
<!-- 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.
|
||||||
|
|
||||||
|
<strong>
|
||||||
|
The latest 1.0.x release of this document can be found
|
||||||
|
[here](http://releases.k8s.io/release-1.0/docs/user-guide/kubectl/kubectl_apply.md).
|
||||||
|
|
||||||
|
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 apply
|
||||||
|
|
||||||
|
Apply a configuration to a resource by filename or stdin
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
|
||||||
|
Apply a configuration to a resource by filename or stdin.
|
||||||
|
|
||||||
|
JSON and YAML formats are accepted.
|
||||||
|
|
||||||
|
```
|
||||||
|
kubectl apply -f FILENAME
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
# Apply the configuration in pod.json to a pod.
|
||||||
|
$ kubectl apply -f ./pod.json
|
||||||
|
|
||||||
|
# Apply the JSON passed into stdin to a pod.
|
||||||
|
$ cat pod.json | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-f, --filename=[]: Filename, directory, or URL to file that contains the configuration to apply
|
||||||
|
-o, --output="": Output mode. Use "-o name" for shorter output (resource/name).
|
||||||
|
--schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema'
|
||||||
|
--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 key 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](kubectl.md) - kubectl controls the Kubernetes cluster manager
|
||||||
|
|
||||||
|
###### Auto generated by spf13/cobra at 2015-10-01 05:36:57.66914652 +0000 UTC
|
||||||
|
|
||||||
|
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
|
||||||
|
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_apply.md?pixel)]()
|
||||||
|
<!-- END MUNGE: GENERATED_ANALYTICS -->
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type debugError interface {
|
||||||
|
DebugError() (msg string, args []interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastAppliedConfigAnnotation is the annotation used to store the previous
|
||||||
|
// configuration of a resource for use in a three way diff by UpdateApplyAnnotation.
|
||||||
|
const LastAppliedConfigAnnotation = kubectlAnnotationPrefix + "last-applied-configuration"
|
||||||
|
|
||||||
|
// GetOriginalConfiguration retrieves the original configuration of the object
|
||||||
|
// from the annotation, or nil if no annotation was found.
|
||||||
|
func GetOriginalConfiguration(info *resource.Info) ([]byte, error) {
|
||||||
|
annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotations == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
original, ok := annotations[LastAppliedConfigAnnotation]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(original), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOriginalConfiguration sets the original configuration of the object
|
||||||
|
// as the annotation on the object for later use in computing a three way patch.
|
||||||
|
func SetOriginalConfiguration(info *resource.Info, original []byte) error {
|
||||||
|
if len(original) < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current annotations from the object.
|
||||||
|
annotations, err := info.Mapping.MetadataAccessor.Annotations(info.Object)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotations == nil {
|
||||||
|
annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations[LastAppliedConfigAnnotation] = string(original)
|
||||||
|
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModifiedConfiguration retrieves the modified configuration of the object.
|
||||||
|
// If annotate is true, it embeds the result as an anotation in the modified
|
||||||
|
// configuration. If an object was read from the command input, it will use that
|
||||||
|
// version of the object. Otherwise, it will use the version from the server.
|
||||||
|
func GetModifiedConfiguration(info *resource.Info, annotate bool) ([]byte, error) {
|
||||||
|
// First serialize the object without the annotation to prevent recursion,
|
||||||
|
// then add that serialization to it as the annotation and serialize it again.
|
||||||
|
var modified []byte
|
||||||
|
if info.VersionedObject != nil {
|
||||||
|
// If an object was read from input, use that version.
|
||||||
|
accessor, err := meta.Accessor(info.VersionedObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current annotations from the object.
|
||||||
|
annotations := accessor.Annotations()
|
||||||
|
if annotations == nil {
|
||||||
|
annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
original := annotations[LastAppliedConfigAnnotation]
|
||||||
|
delete(annotations, LastAppliedConfigAnnotation)
|
||||||
|
accessor.SetAnnotations(annotations)
|
||||||
|
modified, err = json.Marshal(info.VersionedObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotate {
|
||||||
|
annotations[LastAppliedConfigAnnotation] = string(modified)
|
||||||
|
accessor.SetAnnotations(annotations)
|
||||||
|
modified, err = json.Marshal(info.VersionedObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the object to its original condition.
|
||||||
|
annotations[LastAppliedConfigAnnotation] = original
|
||||||
|
accessor.SetAnnotations(annotations)
|
||||||
|
} else {
|
||||||
|
// Otherwise, use the server side version of the object.
|
||||||
|
accessor := info.Mapping.MetadataAccessor
|
||||||
|
// Get the current annotations from the object.
|
||||||
|
annotations, err := accessor.Annotations(info.Object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotations == nil {
|
||||||
|
annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
original := annotations[LastAppliedConfigAnnotation]
|
||||||
|
delete(annotations, LastAppliedConfigAnnotation)
|
||||||
|
if err := accessor.SetAnnotations(info.Object, annotations); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
modified, err = info.Mapping.Codec.Encode(info.Object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotate {
|
||||||
|
annotations[LastAppliedConfigAnnotation] = string(modified)
|
||||||
|
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
modified, err = info.Mapping.Codec.Encode(info.Object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the object to its original condition.
|
||||||
|
annotations[LastAppliedConfigAnnotation] = original
|
||||||
|
if err := info.Mapping.MetadataAccessor.SetAnnotations(info.Object, annotations); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified, nil
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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/api"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||||
|
"k8s.io/kubernetes/pkg/util/strategicpatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyOptions stores cmd.Flag values for apply. As new fields are added, add them here instead of
|
||||||
|
// referencing the cmd.Flags()
|
||||||
|
type ApplyOptions struct {
|
||||||
|
Filenames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
apply_long = `Apply a configuration to a resource by filename or stdin.
|
||||||
|
|
||||||
|
JSON and YAML formats are accepted.`
|
||||||
|
apply_example = `# Apply the configuration in pod.json to a pod.
|
||||||
|
$ kubectl apply -f ./pod.json
|
||||||
|
|
||||||
|
# Apply the JSON passed into stdin to a pod.
|
||||||
|
$ cat pod.json | kubectl apply -f -`
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command {
|
||||||
|
options := &ApplyOptions{}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "apply -f FILENAME",
|
||||||
|
Short: "Apply a configuration to a resource by filename or stdin",
|
||||||
|
Long: apply_long,
|
||||||
|
Example: apply_example,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cmdutil.CheckErr(validateArgs(cmd, args))
|
||||||
|
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
|
||||||
|
cmdutil.CheckErr(RunApply(f, cmd, out, options))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
usage := "Filename, directory, or URL to file that contains the configuration to apply"
|
||||||
|
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
|
||||||
|
cmd.MarkFlagRequired("filename")
|
||||||
|
cmdutil.AddValidateFlags(cmd)
|
||||||
|
cmdutil.AddOutputFlagsForMutation(cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateArgs(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 0 {
|
||||||
|
return cmdutil.UsageError(cmd, "Unexpected args: %v", args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *ApplyOptions) error {
|
||||||
|
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
|
||||||
|
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper, typer := f.Object()
|
||||||
|
r := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
|
||||||
|
Schema(schema).
|
||||||
|
ContinueOnError().
|
||||||
|
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||||
|
FilenameParam(enforceNamespace, options.Filenames...).
|
||||||
|
Flatten().
|
||||||
|
Do()
|
||||||
|
err = r.Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
err = r.Visit(func(info *resource.Info, err error) error {
|
||||||
|
// In this method, info.Object contains the object retrieved from the server
|
||||||
|
// and info.VersionedObject contains the object decoded from the input source.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the modified configuration of the object. Embed the result
|
||||||
|
// as an annotation in the modified configuration, so that it will appear
|
||||||
|
// in the patch sent to the server.
|
||||||
|
modified, err := kubectl.GetModifiedConfiguration(info, true)
|
||||||
|
if err != nil {
|
||||||
|
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving modified configuration from:\n%v\nfor:", info), info.Source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := info.Get(); err != nil {
|
||||||
|
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the current configuration of the object from the server.
|
||||||
|
current, err := info.Mapping.Codec.Encode(info.Object)
|
||||||
|
if err != nil {
|
||||||
|
return cmdutil.AddSourceToErr(fmt.Sprintf("serializing current configuration from:\n%v\nfor:", info), info.Source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the original configuration of the object from the annotation.
|
||||||
|
original, err := kubectl.GetOriginalConfiguration(info)
|
||||||
|
if err != nil {
|
||||||
|
return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving original configuration from:\n%v\nfor:", info), info.Source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a three way strategic merge patch to send to server.
|
||||||
|
patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, info.VersionedObject, false)
|
||||||
|
if err != nil {
|
||||||
|
format := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfrom:\n%v\nfor:"
|
||||||
|
return cmdutil.AddSourceToErr(fmt.Sprintf(format, original, modified, current, info), info.Source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper := resource.NewHelper(info.Client, info.Mapping)
|
||||||
|
_, err = helper.Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch)
|
||||||
|
if err != nil {
|
||||||
|
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "configured")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return fmt.Errorf("no objects passed to apply")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,260 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyExtraArgsFail(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
f, _, _ := NewAPIFactory()
|
||||||
|
c := NewCmdApply(f, buf)
|
||||||
|
if validateApplyArgs(c, []string{"rc"}) == nil {
|
||||||
|
t.Fatalf("unexpected non-error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateApplyArgs(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 0 {
|
||||||
|
return cmdutil.UsageError(cmd, "Unexpected args: %v", args)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
filenameRC = "../../../examples/guestbook/redis-master-controller.yaml"
|
||||||
|
filenameSVC = "../../../examples/guestbook/frontend-service.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readBytesFromFile(t *testing.T, filename string) []byte {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func readReplicationControllerFromFile(t *testing.T, filename string) *api.ReplicationController {
|
||||||
|
data := readBytesFromFile(t, filename)
|
||||||
|
rc := api.ReplicationController{}
|
||||||
|
// TODO(jackgr): Replace with a call to testapi.Codec().Decode().
|
||||||
|
if err := yaml.Unmarshal(data, &rc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func readServiceFromFile(t *testing.T, filename string) *api.Service {
|
||||||
|
data := readBytesFromFile(t, filename)
|
||||||
|
svc := api.Service{}
|
||||||
|
// TODO(jackgr): Replace with a call to testapi.Codec().Decode().
|
||||||
|
if err := yaml.Unmarshal(data, &svc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &svc
|
||||||
|
}
|
||||||
|
|
||||||
|
func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object, kind string) (string, []byte) {
|
||||||
|
originalMeta, err := api.ObjectMetaFor(originalObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
originalMeta.Labels["DELETE_ME"] = "DELETE_ME"
|
||||||
|
original, err := json.Marshal(originalObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMeta, err := api.ObjectMetaFor(currentObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentMeta.Annotations == nil {
|
||||||
|
currentMeta.Annotations = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMeta.Annotations[kubectl.LastAppliedConfigAnnotation] = string(original)
|
||||||
|
current, err := json.Marshal(currentObj)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentMeta.Name, current
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAndAnnotateReplicationController(t *testing.T, filename string) (string, []byte) {
|
||||||
|
rc1 := readReplicationControllerFromFile(t, filename)
|
||||||
|
rc2 := readReplicationControllerFromFile(t, filename)
|
||||||
|
return annotateRuntimeObject(t, rc1, rc2, "ReplicationController")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAndAnnotateService(t *testing.T, filename string) (string, []byte) {
|
||||||
|
svc1 := readServiceFromFile(t, filename)
|
||||||
|
svc2 := readServiceFromFile(t, filename)
|
||||||
|
return annotateRuntimeObject(t, svc1, svc2, "Service")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatePatchApplication(t *testing.T, req *http.Request) {
|
||||||
|
patch, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchMap := map[string]interface{}{}
|
||||||
|
if err := json.Unmarshal(patch, &patchMap); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"})
|
||||||
|
if _, ok := annotationsMap[kubectl.LastAppliedConfigAnnotation]; !ok {
|
||||||
|
t.Fatalf("patch does not contain annotation:\n%s\n", patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
labelMap := walkMapPath(t, patchMap, []string{"metadata", "labels"})
|
||||||
|
if deleteMe, ok := labelMap["DELETE_ME"]; !ok || deleteMe != nil {
|
||||||
|
t.Fatalf("patch does not remove deleted key: DELETE_ME:\n%s\n", patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[string]interface{} {
|
||||||
|
finish := start
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
var ok bool
|
||||||
|
finish, ok = finish[path[i]].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("key:%s of path:%v not found in map:%v", path[i], path, start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finish
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyObject(t *testing.T) {
|
||||||
|
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
|
||||||
|
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
|
||||||
|
|
||||||
|
f, tf, codec := NewAPIFactory()
|
||||||
|
tf.Printer = &testPrinter{}
|
||||||
|
tf.Client = &fake.RESTClient{
|
||||||
|
Codec: codec,
|
||||||
|
Client: fake.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
|
case p == pathRC && m == "GET":
|
||||||
|
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
|
||||||
|
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
|
||||||
|
case p == pathRC && m == "PATCH":
|
||||||
|
validatePatchApplication(t, req)
|
||||||
|
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
|
||||||
|
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
tf.Namespace = "test"
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
cmd := NewCmdApply(f, buf)
|
||||||
|
cmd.Flags().Set("filename", filenameRC)
|
||||||
|
cmd.Flags().Set("output", "name")
|
||||||
|
cmd.Run(cmd, []string{})
|
||||||
|
|
||||||
|
// uses the name from the file, not the response
|
||||||
|
expectRC := "replicationcontroller/" + nameRC + "\n"
|
||||||
|
if buf.String() != expectRC {
|
||||||
|
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expectRC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyMultipleObject(t *testing.T) {
|
||||||
|
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
|
||||||
|
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
|
||||||
|
|
||||||
|
nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
|
||||||
|
pathSVC := "/namespaces/test/services/" + nameSVC
|
||||||
|
|
||||||
|
f, tf, codec := NewAPIFactory()
|
||||||
|
tf.Printer = &testPrinter{}
|
||||||
|
tf.Client = &fake.RESTClient{
|
||||||
|
Codec: codec,
|
||||||
|
Client: fake.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
|
case p == pathRC && m == "GET":
|
||||||
|
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
|
||||||
|
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
|
||||||
|
case p == pathRC && m == "PATCH":
|
||||||
|
validatePatchApplication(t, req)
|
||||||
|
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
|
||||||
|
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
|
||||||
|
case p == pathSVC && m == "GET":
|
||||||
|
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
|
||||||
|
return &http.Response{StatusCode: 200, Body: bodySVC}, nil
|
||||||
|
case p == pathSVC && m == "PATCH":
|
||||||
|
validatePatchApplication(t, req)
|
||||||
|
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
|
||||||
|
return &http.Response{StatusCode: 200, Body: bodySVC}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
tf.Namespace = "test"
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
cmd := NewCmdApply(f, buf)
|
||||||
|
cmd.Flags().Set("filename", filenameRC)
|
||||||
|
cmd.Flags().Set("filename", filenameSVC)
|
||||||
|
cmd.Flags().Set("output", "name")
|
||||||
|
|
||||||
|
cmd.Run(cmd, []string{})
|
||||||
|
|
||||||
|
// Names should come from the REST response, NOT the files
|
||||||
|
expectRC := "replicationcontroller/" + nameRC + "\n"
|
||||||
|
expectSVC := "service/" + nameSVC + "\n"
|
||||||
|
expect := expectRC + expectSVC
|
||||||
|
if buf.String() != expect {
|
||||||
|
t.Fatalf("unexpected output: %s\nexpected: %s", buf.String(), expect)
|
||||||
|
}
|
||||||
|
}
|
|
@ -150,6 +150,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`,
|
||||||
cmds.AddCommand(NewCmdPatch(f, out))
|
cmds.AddCommand(NewCmdPatch(f, out))
|
||||||
cmds.AddCommand(NewCmdDelete(f, out))
|
cmds.AddCommand(NewCmdDelete(f, out))
|
||||||
cmds.AddCommand(NewCmdEdit(f, out))
|
cmds.AddCommand(NewCmdEdit(f, out))
|
||||||
|
cmds.AddCommand(NewCmdApply(f, out))
|
||||||
|
|
||||||
cmds.AddCommand(NewCmdNamespace(out))
|
cmds.AddCommand(NewCmdNamespace(out))
|
||||||
cmds.AddCommand(NewCmdLog(f, out))
|
cmds.AddCommand(NewCmdLog(f, out))
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package e2e
|
package e2e
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -32,10 +33,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/fields"
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/util/wait"
|
"k8s.io/kubernetes/pkg/util/wait"
|
||||||
|
|
||||||
|
@ -226,6 +230,25 @@ var _ = Describe("Kubectl client", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("Kubectl apply", func() {
|
||||||
|
It("should apply a new configuration to an existing RC", func() {
|
||||||
|
mkpath := func(file string) string {
|
||||||
|
return filepath.Join(testContext.RepoRoot, "examples/guestbook-go", file)
|
||||||
|
}
|
||||||
|
controllerJson := mkpath("redis-master-controller.json")
|
||||||
|
nsFlag := fmt.Sprintf("--namespace=%v", ns)
|
||||||
|
By("creating Redis RC")
|
||||||
|
runKubectl("create", "-f", controllerJson, nsFlag)
|
||||||
|
By("applying a modified configuration")
|
||||||
|
stdin := modifyReplicationControllerConfiguration(controllerJson)
|
||||||
|
newKubectlCommand("apply", "-f", "-", nsFlag).
|
||||||
|
withStdinReader(stdin).
|
||||||
|
exec()
|
||||||
|
By("checking the result")
|
||||||
|
forEachReplicationController(c, ns, "app", "redis", validateReplicationControllerConfiguration)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Describe("Kubectl cluster-info", func() {
|
Describe("Kubectl cluster-info", func() {
|
||||||
It("should check if Kubernetes master services is included in cluster-info", func() {
|
It("should check if Kubernetes master services is included in cluster-info", func() {
|
||||||
By("validating cluster-info")
|
By("validating cluster-info")
|
||||||
|
@ -812,6 +835,77 @@ type updateDemoData struct {
|
||||||
Image string
|
Image string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const applyTestLabel = "kubectl.kubernetes.io/apply-test"
|
||||||
|
|
||||||
|
func readBytesFromFile(filename string) []byte {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
Failf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
Failf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func readReplicationControllerFromFile(filename string) *api.ReplicationController {
|
||||||
|
data := readBytesFromFile(filename)
|
||||||
|
rc := api.ReplicationController{}
|
||||||
|
if err := yaml.Unmarshal(data, &rc); err != nil {
|
||||||
|
Failf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func modifyReplicationControllerConfiguration(filename string) io.Reader {
|
||||||
|
rc := readReplicationControllerFromFile(filename)
|
||||||
|
rc.Labels[applyTestLabel] = "ADDED"
|
||||||
|
rc.Spec.Selector[applyTestLabel] = "ADDED"
|
||||||
|
rc.Spec.Template.Labels[applyTestLabel] = "ADDED"
|
||||||
|
data, err := json.Marshal(rc)
|
||||||
|
if err != nil {
|
||||||
|
Failf("json marshal failed: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.NewReader(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func forEachReplicationController(c *client.Client, ns, selectorKey, selectorValue string, fn func(api.ReplicationController)) {
|
||||||
|
var rcs *api.ReplicationControllerList
|
||||||
|
var err error
|
||||||
|
for t := time.Now(); time.Since(t) < podListTimeout; time.Sleep(poll) {
|
||||||
|
rcs, err = c.ReplicationControllers(ns).List(labels.SelectorFromSet(labels.Set(map[string]string{selectorKey: selectorValue})))
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
if len(rcs.Items) > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rcs == nil || len(rcs.Items) == 0 {
|
||||||
|
Failf("No replication controllers found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rc := range rcs.Items {
|
||||||
|
fn(rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateReplicationControllerConfiguration(rc api.ReplicationController) {
|
||||||
|
if rc.Name == "redis-master" {
|
||||||
|
if _, ok := rc.Annotations[kubectl.LastAppliedConfigAnnotation]; !ok {
|
||||||
|
Failf("Annotation not found in modified configuration:\n%v\n", rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := rc.Labels[applyTestLabel]; !ok || value != "ADDED" {
|
||||||
|
Failf("Added label %s not found in modified configuration:\n%v\n", applyTestLabel, rc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getUDData creates a validator function based on the input string (i.e. kitten.jpg).
|
// getUDData creates a validator function based on the input string (i.e. kitten.jpg).
|
||||||
// For example, if you send "kitten.jpg", this function veridies that the image jpg = kitten.jpg
|
// For example, if you send "kitten.jpg", this function veridies that the image jpg = kitten.jpg
|
||||||
// in the container's json field.
|
// in the container's json field.
|
||||||
|
|
Loading…
Reference in New Issue