mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Merge pull request #14621 from jackgr/kubectl_apply
Add the kubectl apply command
This commit is contained in:
		@@ -2,6 +2,7 @@
 | 
			
		||||
contrib/completions/bash/kubectl
 | 
			
		||||
docs/man/man1/kubectl-annotate.1
 | 
			
		||||
docs/man/man1/kubectl-api-versions.1
 | 
			
		||||
docs/man/man1/kubectl-apply.1
 | 
			
		||||
docs/man/man1/kubectl-attach.1
 | 
			
		||||
docs/man/man1/kubectl-cluster-info.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_annotate.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_cluster-info.md
 | 
			
		||||
docs/user-guide/kubectl/kubectl_config.md
 | 
			
		||||
 
 | 
			
		||||
@@ -504,6 +504,33 @@ _kubectl_edit()
 | 
			
		||||
    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()
 | 
			
		||||
{
 | 
			
		||||
    last_command="kubectl_namespace"
 | 
			
		||||
@@ -1136,6 +1163,7 @@ _kubectl()
 | 
			
		||||
    commands+=("patch")
 | 
			
		||||
    commands+=("delete")
 | 
			
		||||
    commands+=("edit")
 | 
			
		||||
    commands+=("apply")
 | 
			
		||||
    commands+=("namespace")
 | 
			
		||||
    commands+=("logs")
 | 
			
		||||
    commands+=("rolling-update")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
kubectl-apply.1
 | 
			
		||||
kubectl-annotate.1
 | 
			
		||||
kubectl-api-versions.1
 | 
			
		||||
kubectl-attach.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										156
									
								
								docs/man/man1/kubectl-apply.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								docs/man/man1/kubectl-apply.1
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
.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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
kubectl.md
 | 
			
		||||
kubectl_apply.md
 | 
			
		||||
kubectl_annotate.md
 | 
			
		||||
kubectl_api-versions.md
 | 
			
		||||
kubectl_attach.md
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,7 @@ kubectl
 | 
			
		||||
 | 
			
		||||
* [kubectl annotate](kubectl_annotate.md)	 - Update the annotations on a resource
 | 
			
		||||
* [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 cluster-info](kubectl_cluster-info.md)	 - Display cluster info
 | 
			
		||||
* [kubectl config](kubectl_config.md)	 - config modifies kubeconfig files
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								docs/user-guide/kubectl/kubectl_apply.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								docs/user-guide/kubectl/kubectl_apply.md
									
									
									
									
									
										Normal file
									
								
							@@ -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 -->
 | 
			
		||||
[]()
 | 
			
		||||
<!-- END MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
							
								
								
									
										164
									
								
								pkg/kubectl/apply.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								pkg/kubectl/apply.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								pkg/kubectl/cmd/apply.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								pkg/kubectl/cmd/apply.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										260
									
								
								pkg/kubectl/cmd/apply_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								pkg/kubectl/cmd/apply_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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(NewCmdDelete(f, out))
 | 
			
		||||
	cmds.AddCommand(NewCmdEdit(f, out))
 | 
			
		||||
	cmds.AddCommand(NewCmdApply(f, out))
 | 
			
		||||
 | 
			
		||||
	cmds.AddCommand(NewCmdNamespace(out))
 | 
			
		||||
	cmds.AddCommand(NewCmdLog(f, out))
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ limitations under the License.
 | 
			
		||||
package e2e
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
@@ -32,10 +33,13 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/ghodss/yaml"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	apierrs "k8s.io/kubernetes/pkg/api/errors"
 | 
			
		||||
	client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/fields"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubectl"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/labels"
 | 
			
		||||
	"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() {
 | 
			
		||||
		It("should check if Kubernetes master services is included in cluster-info", func() {
 | 
			
		||||
			By("validating cluster-info")
 | 
			
		||||
@@ -812,6 +835,77 @@ type updateDemoData struct {
 | 
			
		||||
	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).
 | 
			
		||||
// For example, if you send "kitten.jpg", this function veridies that the image jpg = kitten.jpg
 | 
			
		||||
// in the container's json field.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user