First part of improved rolling update, allow dynamic next replication controller generation.

This commit is contained in:
Brendan Burns 2015-04-23 16:27:19 -07:00
parent e8b28c59c6
commit 217e6ff0d0
32 changed files with 440 additions and 60 deletions

View File

@ -405,11 +405,20 @@ _kubectl_rolling-update()
flags_with_completion=() flags_with_completion=()
flags_completion=() flags_completion=()
flags+=("--deployment-label-key=")
flags+=("--dry-run")
flags+=("--filename=") flags+=("--filename=")
two_word_flags+=("-f") two_word_flags+=("-f")
flags+=("--help") flags+=("--help")
flags+=("-h") flags+=("-h")
flags+=("--image=")
flags+=("--no-headers")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--output-version=")
flags+=("--poll-interval=") flags+=("--poll-interval=")
flags+=("--template=")
two_word_flags+=("-t")
flags+=("--timeout=") flags+=("--timeout=")
flags+=("--update-period=") flags+=("--update-period=")

View File

@ -66,4 +66,4 @@ kubectl
* [kubectl update](kubectl_update.md) - Update a resource by filename or stdin. * [kubectl update](kubectl_update.md) - Update a resource by filename or stdin.
* [kubectl version](kubectl_version.md) - Print the client and server version information. * [kubectl version](kubectl_version.md) - Print the client and server version information.
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.406236586 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.641615471 +0000 UTC

View File

@ -50,4 +50,4 @@ kubectl api-versions
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.405815046 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.640579424 +0000 UTC

View File

@ -50,4 +50,4 @@ kubectl cluster-info
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.405639667 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.640382538 +0000 UTC

View File

@ -63,4 +63,4 @@ kubectl config SUBCOMMAND
* [kubectl config use-context](kubectl_config_use-context.md) - Sets the current-context in a kubeconfig file * [kubectl config use-context](kubectl_config_use-context.md) - Sets the current-context in a kubeconfig file
* [kubectl config view](kubectl_config_view.md) - displays Merged kubeconfig settings or a specified kubeconfig file. * [kubectl config view](kubectl_config_view.md) - displays Merged kubeconfig settings or a specified kubeconfig file.
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.405466384 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.640154327 +0000 UTC

View File

@ -65,4 +65,4 @@ $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.404357726 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.638291301 +0000 UTC

View File

@ -58,4 +58,4 @@ $ kubectl config set-context gce --user=cluster-admin
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.40472172 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.638842283 +0000 UTC

View File

@ -78,4 +78,4 @@ $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt -
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.40454463 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.638596132 +0000 UTC

View File

@ -52,4 +52,4 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.404916515 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.639475859 +0000 UTC

View File

@ -51,4 +51,4 @@ kubectl config unset PROPERTY_NAME
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.405094144 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.639702273 +0000 UTC

View File

@ -50,4 +50,4 @@ kubectl config use-context CONTEXT_NAME
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.405277784 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.639928664 +0000 UTC

View File

@ -73,4 +73,4 @@ $ kubectl config view -o template --template='{{range .users}}{{ if eq .name "e2
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.404151238 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.638011058 +0000 UTC

View File

@ -63,4 +63,4 @@ $ cat pod.json | kubectl create -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.400230448 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.633893126 +0000 UTC

View File

@ -81,4 +81,4 @@ $ kubectl delete pods --all
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.400618492 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.634491315 +0000 UTC

View File

@ -63,4 +63,4 @@ $ kubectl describe pods/nginx
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 21:21:05.485819349 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.633584776 +0000 UTC

View File

@ -64,4 +64,4 @@ $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.401622652 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.636226358 +0000 UTC

View File

@ -82,4 +82,4 @@ $ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.403631642 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.637486796 +0000 UTC

View File

@ -85,4 +85,4 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.399795546 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.633184922 +0000 UTC

View File

@ -81,4 +81,4 @@ $ kubectl label pods foo bar-
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.403876136 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.637727935 +0000 UTC

View File

@ -62,4 +62,4 @@ $ kubectl log -f 123456-7890 ruby-container
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.400998066 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.634970712 +0000 UTC

View File

@ -53,4 +53,4 @@ kubectl namespace [namespace]
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.400806182 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.634703492 +0000 UTC

View File

@ -68,4 +68,4 @@ $ kubectl port-forward -p mypod 0:5000
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.40181203 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.636454335 +0000 UTC

View File

@ -65,4 +65,4 @@ $ kubectl proxy --api-prefix=k8s-api
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.402034031 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.636689051 +0000 UTC

View File

@ -68,4 +68,4 @@ $ kubectl resize --current-replicas=2 --replicas=3 replicationcontrollers foo
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.401412855 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.635916597 +0000 UTC

View File

@ -23,14 +23,25 @@ $ kubectl rolling-update frontend-v1 -f frontend-v2.json
// Update pods of frontend-v1 using JSON data passed into stdin. // Update pods of frontend-v1 using JSON data passed into stdin.
$ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f - $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
// Update the pods of frontend-v1 to frontend-v2 by just changing the image
$ kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2
``` ```
### Options ### Options
``` ```
--deployment-label-key="deployment": The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise
--dry-run=false: If true, print out the changes that would be made, but don't actually make them.
-f, --filename="": Filename or URL to file to use to create the new controller. -f, --filename="": Filename or URL to file to use to create the new controller.
-h, --help=false: help for rolling-update -h, --help=false: help for rolling-update
--image="": Image to upgrade the controller to. Can not be used with --filename/-f
--no-headers=false: When using the default output, don't print headers.
-o, --output="": Output format. One of: json|yaml|template|templatefile.
--output-version="": Output the formatted object with the given version (default api-version).
--poll-interval="3s": Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". --poll-interval="3s": Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
-t, --template="": Template string or path to template file to use when -o=template or -o=templatefile. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]
--timeout="5m0s": Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". --timeout="5m0s": Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
--update-period="1m0s": Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". --update-period="1m0s": Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
``` ```
@ -68,4 +79,4 @@ $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.401210692 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-24 04:16:19.186748349 +0000 UTC

View File

@ -78,4 +78,4 @@ $ kubectl run-container nginx --image=nginx --overrides='{ "apiVersion": "v1beta
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.402296924 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.636970945 +0000 UTC

View File

@ -72,4 +72,4 @@ $ kubectl stop -f path/to/resources
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.402507426 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.637194082 +0000 UTC

View File

@ -67,4 +67,4 @@ $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState":
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.400435658 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.634205944 +0000 UTC

View File

@ -51,4 +51,4 @@ kubectl version
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-23 00:47:55.405995955 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-23 23:29:05.640847018 +0000 UTC

View File

@ -22,6 +22,14 @@ existing controller and overwrite at least one (common) label in its replicaSele
.SH OPTIONS .SH OPTIONS
.PP
\fB\-\-deployment\-label\-key\fP="deployment"
The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when \-\-image is specified, ignored otherwise
.PP
\fB\-\-dry\-run\fP=false
If true, print out the changes that would be made, but don't actually make them.
.PP .PP
\fB\-f\fP, \fB\-\-filename\fP="" \fB\-f\fP, \fB\-\-filename\fP=""
Filename or URL to file to use to create the new controller. Filename or URL to file to use to create the new controller.
@ -30,10 +38,31 @@ existing controller and overwrite at least one (common) label in its replicaSele
\fB\-h\fP, \fB\-\-help\fP=false \fB\-h\fP, \fB\-\-help\fP=false
help for rolling\-update help for rolling\-update
.PP
\fB\-\-image\fP=""
Image to upgrade the controller to. Can not be used with \-\-filename/\-f
.PP
\fB\-\-no\-headers\fP=false
When using the default output, don't print headers.
.PP
\fB\-o\fP, \fB\-\-output\fP=""
Output format. One of: json|yaml|template|templatefile.
.PP
\fB\-\-output\-version\fP=""
Output the formatted object with the given version (default api\-version).
.PP .PP
\fB\-\-poll\-interval\fP="3s" \fB\-\-poll\-interval\fP="3s"
Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
.PP
\fB\-t\fP, \fB\-\-template\fP=""
Template string or path to template file to use when \-o=template or \-o=templatefile. The template format is golang templates [
\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]]
.PP .PP
\fB\-\-timeout\fP="5m0s" \fB\-\-timeout\fP="5m0s"
Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
@ -156,6 +185,10 @@ $ kubectl rolling\-update frontend\-v1 \-f frontend\-v2.json
// Update pods of frontend\-v1 using JSON data passed into stdin. // Update pods of frontend\-v1 using JSON data passed into stdin.
$ cat frontend\-v2.json | kubectl rolling\-update frontend\-v1 \-f \- $ cat frontend\-v2.json | kubectl rolling\-update frontend\-v1 \-f \-
// Update the pods of frontend\-v1 to frontend\-v2 by just changing the image
$ kubectl rolling\-update frontend\-v1 frontend\-v2 \-\-image=image:v2
.fi .fi
.RE .RE

View File

@ -17,14 +17,22 @@ limitations under the License.
package cmd package cmd
import ( import (
"bytes"
"crypto/md5"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -41,7 +49,11 @@ existing controller and overwrite at least one (common) label in its replicaSele
$ kubectl rolling-update frontend-v1 -f frontend-v2.json $ kubectl rolling-update frontend-v1 -f frontend-v2.json
// Update pods of frontend-v1 using JSON data passed into stdin. // Update pods of frontend-v1 using JSON data passed into stdin.
$ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -` $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
// Update the pods of frontend-v1 to frontend-v2 by just changing the image
$ kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2
`
) )
func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
@ -61,33 +73,70 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmd.Flags().String("poll-interval", pollInterval, `Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) cmd.Flags().String("poll-interval", pollInterval, `Time delay between polling controller status after update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
cmd.Flags().String("timeout", timeout, `Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) cmd.Flags().String("timeout", timeout, `Max time to wait for a controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to create the new controller.") cmd.Flags().StringP("filename", "f", "", "Filename or URL to file to use to create the new controller.")
cmd.Flags().String("image", "", "Image to upgrade the controller to. Can not be used with --filename/-f")
cmd.Flags().String("deployment-label-key", "deployment", "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise")
cmd.Flags().Bool("dry-run", false, "If true, print out the changes that would be made, but don't actually make them.")
cmdutil.AddPrinterFlags(cmd)
return cmd return cmd
} }
func validateArguments(cmd *cobra.Command, args []string) (deploymentKey, filename, image, oldName string, err error) {
deploymentKey = cmdutil.GetFlagString(cmd, "deployment-label-key")
filename = cmdutil.GetFlagString(cmd, "filename")
image = cmdutil.GetFlagString(cmd, "image")
if len(deploymentKey) == 0 {
return "", "", "", "", cmdutil.UsageError(cmd, "--deployment-label-key can not be empty")
}
if len(filename) == 0 && len(image) == 0 {
return "", "", "", "", cmdutil.UsageError(cmd, "Must specify --filename or --image for new controller")
}
if len(filename) != 0 && len(image) != 0 {
return "", "", "", "", cmdutil.UsageError(cmd, "--filename and --image can not both be specified")
}
if len(image) > 0 && len(args) < 2 {
return "", "", "", "", cmdutil.UsageError(cmd, "You must specify a name for your new controller")
}
if len(args) < 1 {
return "", "", "", "", cmdutil.UsageError(cmd, "Must specify the controller to update")
}
return deploymentKey, filename, image, args[0], nil
}
func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
if os.Args[1] == "rollingupdate" { if os.Args[1] == "rollingupdate" {
printDeprecationWarning("rolling-update", "rollingupdate") printDeprecationWarning("rolling-update", "rollingupdate")
} }
deploymentKey, filename, image, oldName, err := validateArguments(cmd, args)
filename := cmdutil.GetFlagString(cmd, "filename") if err != nil {
if len(filename) == 0 { return err
return cmdutil.UsageError(cmd, "Must specify filename for new controller")
} }
period := cmdutil.GetFlagDuration(cmd, "update-period") period := cmdutil.GetFlagDuration(cmd, "update-period")
interval := cmdutil.GetFlagDuration(cmd, "poll-interval") interval := cmdutil.GetFlagDuration(cmd, "poll-interval")
timeout := cmdutil.GetFlagDuration(cmd, "timeout") timeout := cmdutil.GetFlagDuration(cmd, "timeout")
if len(args) != 1 { dryrun := cmdutil.GetFlagBool(cmd, "dry-run")
return cmdutil.UsageError(cmd, "Must specify the controller to update")
}
oldName := args[0]
cmdNamespace, err := f.DefaultNamespace() cmdNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
} }
client, err := f.Client()
if err != nil {
return err
}
// fetch rc
oldRc, err := client.ReplicationControllers(cmdNamespace).Get(oldName)
if err != nil {
return err
}
mapper, typer := f.Object() mapper, typer := f.Object()
// TODO: use resource.Builder instead var newRc *api.ReplicationController
if len(filename) != 0 {
obj, err := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()). obj, err := resource.NewBuilder(mapper, typer, f.ClientMapperForCommand()).
NamespaceParam(cmdNamespace).RequireNamespace(). NamespaceParam(cmdNamespace).RequireNamespace().
FilenameParam(filename). FilenameParam(filename).
@ -96,29 +145,53 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if err != nil { if err != nil {
return err return err
} }
newRc, ok := obj.(*api.ReplicationController) var ok bool
newRc, ok = obj.(*api.ReplicationController)
if !ok { if !ok {
return cmdutil.UsageError(cmd, "%s does not specify a valid ReplicationController", filename) return cmdutil.UsageError(cmd, "%s does not specify a valid ReplicationController", filename)
} }
}
if len(image) != 0 {
newName := args[1]
var err error
// load the old RC into the "new" RC
if newRc, err = client.ReplicationControllers(cmdNamespace).Get(oldName); err != nil {
return err
}
if len(newRc.Spec.Template.Spec.Containers) > 1 {
// TODO: support multi-container image update.
return errors.New("Image update is not supported for multi-container pods")
}
if len(newRc.Spec.Template.Spec.Containers) == 0 {
return cmdutil.UsageError(cmd, "Pod has no containers! (%v)", newRc)
}
newRc.Spec.Template.Spec.Containers[0].Image = image
newHash, err := hashObject(newRc, client.Codec)
if err != nil {
return err
}
newRc.Name = newName
newRc.Spec.Selector[deploymentKey] = newHash
newRc.Spec.Template.Labels[deploymentKey] = newHash
newRc.ResourceVersion = ""
if _, found := oldRc.Spec.Selector[deploymentKey]; !found {
if err := addDeploymentKeyToReplicationController(oldRc, client, deploymentKey, cmdNamespace, out); err != nil {
return err
}
}
}
newName := newRc.Name newName := newRc.Name
if oldName == newName { if oldName == newName {
return cmdutil.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s", return cmdutil.UsageError(cmd, "%s cannot have the same name as the existing ReplicationController %s",
filename, oldName) filename, oldName)
} }
client, err := f.Client()
if err != nil {
return err
}
updater := kubectl.NewRollingUpdater(newRc.Namespace, kubectl.NewRollingUpdaterClient(client)) updater := kubectl.NewRollingUpdater(newRc.Namespace, kubectl.NewRollingUpdaterClient(client))
// fetch rc
oldRc, err := client.ReplicationControllers(newRc.Namespace).Get(oldName)
if err != nil {
return err
}
var hasLabel bool var hasLabel bool
for key, oldValue := range oldRc.Spec.Selector { for key, oldValue := range oldRc.Spec.Selector {
if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue { if newValue, ok := newRc.Spec.Selector[key]; ok && newValue != oldValue {
@ -134,6 +207,18 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if newRc.Spec.Replicas == 0 { if newRc.Spec.Replicas == 0 {
newRc.Spec.Replicas = oldRc.Spec.Replicas newRc.Spec.Replicas = oldRc.Spec.Replicas
} }
if dryrun {
oldRcData := &bytes.Buffer{}
if err := f.PrintObject(cmd, oldRc, oldRcData); err != nil {
return err
}
newRcData := &bytes.Buffer{}
if err := f.PrintObject(cmd, newRc, newRcData); err != nil {
return err
}
fmt.Fprintf(out, "Rolling from:\n%s\nTo:\n%s\n", string(oldRcData.Bytes()), string(newRcData.Bytes()))
return nil
}
err = updater.Update(&kubectl.RollingUpdaterConfig{ err = updater.Update(&kubectl.RollingUpdaterConfig{
Out: out, Out: out,
OldRc: oldRc, OldRc: oldRc,
@ -150,3 +235,68 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
fmt.Fprintf(out, "%s\n", newName) fmt.Fprintf(out, "%s\n", newName)
return nil return nil
} }
func hashObject(obj runtime.Object, codec runtime.Codec) (string, error) {
data, err := codec.Encode(obj)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", md5.Sum(data)), nil
}
const MaxRetries = 3
func addDeploymentKeyToReplicationController(oldRc *api.ReplicationController, client *client.Client, deploymentKey, namespace string, out io.Writer) error {
oldHash, err := hashObject(oldRc, client.Codec)
if err != nil {
return err
}
// Update all labels to include the new hash, so they are correctly adopted
// TODO: extract the code from the label command and re-use it here.
podList, err := client.Pods(namespace).List(labels.SelectorFromSet(oldRc.Spec.Selector), fields.Everything())
if err != nil {
return err
}
for ix := range podList.Items {
pod := &podList.Items[ix]
if pod.Labels == nil {
pod.Labels = map[string]string{
deploymentKey: oldHash,
}
} else {
pod.Labels[deploymentKey] = oldHash
}
err = nil
delay := 3
for i := 0; i < MaxRetries; i++ {
_, err = client.Pods(namespace).Update(pod)
if err != nil {
fmt.Fprint(out, "Error updating pod (%v), retrying after %d seconds", err, delay)
time.Sleep(time.Second * time.Duration(delay))
delay *= delay
} else {
break
}
}
if err != nil {
return err
}
}
if oldRc.Spec.Selector == nil {
oldRc.Spec.Selector = map[string]string{}
}
if oldRc.Spec.Template.Labels == nil {
oldRc.Spec.Template.Labels = map[string]string{}
}
oldRc.Spec.Selector[deploymentKey] = oldHash
oldRc.Spec.Template.Labels[deploymentKey] = oldHash
if _, err := client.ReplicationControllers(namespace).Update(oldRc); err != nil {
return err
}
// Note there is still a race here, if a pod was created during the update phase.
// It's unlikely, but it could happen, and if it does, we'll create extra pods.
// TODO: Clean up orphaned pods here.
return nil
}

View File

@ -0,0 +1,177 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"net/http"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
func TestValidateArgs(t *testing.T) {
f, _, _ := NewAPIFactory()
tests := []struct {
flags map[string]string
args []string
expectErr bool
testName string
}{
{
expectErr: true,
testName: "nothing",
},
{
flags: map[string]string{},
args: []string{"foo"},
expectErr: true,
testName: "no file, no image",
},
{
flags: map[string]string{
"filename": "bar.yaml",
},
args: []string{"foo"},
testName: "valid file example",
},
{
flags: map[string]string{
"image": "foo:v2",
},
args: []string{"foo"},
expectErr: true,
testName: "missing second image name",
},
{
flags: map[string]string{
"image": "foo:v2",
},
args: []string{"foo", "foo-v2"},
testName: "valid image example",
},
{
flags: map[string]string{
"image": "foo:v2",
"filename": "bar.yaml",
},
args: []string{"foo", "foo-v2"},
expectErr: true,
testName: "both filename and image example",
},
}
for _, test := range tests {
out := &bytes.Buffer{}
cmd := NewCmdRollingUpdate(f, out)
if test.flags != nil {
for key, val := range test.flags {
cmd.Flags().Set(key, val)
}
}
_, _, _, _, err := validateArguments(cmd, test.args)
if err != nil && !test.expectErr {
t.Errorf("unexpected error: %v (%s)", err, test.testName)
}
if err == nil && test.expectErr {
t.Error("unexpected non-error (%s)", test.testName)
}
}
}
func TestAddDeploymentHash(t *testing.T) {
buf := &bytes.Buffer{}
f, tf, codec := NewAPIFactory()
rc := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{Name: "rc"},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{
"foo": "bar",
},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
}
podList := &api.PodList{
Items: []api.Pod{
{ObjectMeta: api.ObjectMeta{Name: "foo"}},
{ObjectMeta: api.ObjectMeta{Name: "bar"}},
{ObjectMeta: api.ObjectMeta{Name: "baz"}},
},
}
seen := util.StringSet{}
updatedRc := false
tf.Client = &client.FakeRESTClient{
Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1beta3/namespaces/default/pods" && m == "GET":
if req.URL.RawQuery != "labelSelector=foo%3Dbar" {
t.Errorf("Unexpected query string: %s", req.URL.RawQuery)
}
return &http.Response{StatusCode: 200, Body: objBody(codec, podList)}, nil
case p == "/api/v1beta3/namespaces/default/pods/foo" && m == "PUT":
seen.Insert("foo")
return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[0])}, nil
case p == "/api/v1beta3/namespaces/default/pods/bar" && m == "PUT":
seen.Insert("bar")
return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[1])}, nil
case p == "/api/v1beta3/namespaces/default/pods/baz" && m == "PUT":
seen.Insert("baz")
return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[2])}, nil
case p == "/api/v1beta3/namespaces/default/replicationcontrollers/rc" && m == "PUT":
updatedRc = true
return &http.Response{StatusCode: 200, Body: objBody(codec, rc)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfig = &client.Config{Version: latest.Version}
tf.Namespace = "test"
client, err := f.Client()
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Fail()
return
}
if err := addDeploymentKeyToReplicationController(rc, client, "hash", api.NamespaceDefault, buf); err != nil {
t.Errorf("unexpected error: %v", err)
}
for _, pod := range podList.Items {
if !seen.Has(pod.Name) {
t.Errorf("Missing update for pod: %s", pod.Name)
}
}
if !updatedRc {
t.Errorf("Failed to update replication controller with new labels")
}
}