mirror of https://github.com/k3s-io/k3s
Add the kubectl apply command.
parent
ddda379b1b
commit
703a3e19aa
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(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")
|
||||
|
@ -811,6 +834,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.
|
||||
|
|
Loading…
Reference in New Issue