From 6a68dbdac4920976bf98f4fefcd216749947f338 Mon Sep 17 00:00:00 2001 From: AdoHe Date: Thu, 8 Sep 2016 08:02:04 +0800 Subject: [PATCH] rollout undo add dry-run implementation --- hack/make-rules/test-cmd.sh | 3 ++ pkg/kubectl/cmd/rollout/rollout_undo.go | 10 +++- pkg/kubectl/cmd/util/factory.go | 5 +- pkg/kubectl/rollback.go | 66 ++++++++++++++++++++++--- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index 4632293708..f8b600f433 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -1931,6 +1931,9 @@ __EOF__ # Update the deployment (revision 2) kubectl apply -f hack/testdata/deployment-revision2.yaml "${kube_flags[@]}" kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:" + # Rollback to revision 1 with dry-run - should be no-op + kubectl rollout undo deployment nginx --to-revision=1 --dry-run=true "${kube_flags[@]}" + kube::test::get_object_assert deployment.extensions "{{range.items}}{{$deployment_image_field}}:{{end}}" "${IMAGE_DEPLOYMENT_R2}:" # Rollback to revision 1 kubectl rollout undo deployment nginx --to-revision=1 "${kube_flags[@]}" sleep 1 diff --git a/pkg/kubectl/cmd/rollout/rollout_undo.go b/pkg/kubectl/cmd/rollout/rollout_undo.go index aa80821dfb..50ab8c2d58 100644 --- a/pkg/kubectl/cmd/rollout/rollout_undo.go +++ b/pkg/kubectl/cmd/rollout/rollout_undo.go @@ -38,6 +38,7 @@ type UndoOptions struct { Typer runtime.ObjectTyper Infos []*resource.Info ToRevision int64 + DryRun bool Out io.Writer Filenames []string @@ -52,7 +53,10 @@ var ( kubectl rollout undo deployment/abc # Rollback to deployment revision 3 - kubectl rollout undo deployment/abc --to-revision=3`) + kubectl rollout undo deployment/abc --to-revision=3 + + # Rollback to the previous deployment with dry-run + kubectl rollout undo --dry-run=true deployment/abc`) ) func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -80,6 +84,7 @@ func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int64("to-revision", 0, "The revision to rollback to. Default to 0 (last revision).") usage := "Filename, directory, or URL to a file identifying the resource to get from a server." kubectl.AddJsonFilenameFlag(cmd, &opts.Filenames, usage) + cmdutil.AddDryRunFlag(cmd) cmdutil.AddRecursiveFlag(cmd, &opts.Recursive) return cmd } @@ -92,6 +97,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i o.ToRevision = cmdutil.GetFlagInt64(cmd, "to-revision") o.Mapper, o.Typer = f.Object(false) o.Out = out + o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { @@ -129,7 +135,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i func (o *UndoOptions) RunUndo() error { allErrs := []error{} for ix, info := range o.Infos { - result, err := o.Rollbackers[ix].Rollback(info.Object, nil, o.ToRevision) + result, err := o.Rollbackers[ix].Rollback(info.Object, nil, o.ToRevision, o.DryRun) if err != nil { allErrs = append(allErrs, cmdutil.AddSourceToErr("undoing", info.Source, err)) continue diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index d61ed1004b..99a1d544d5 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -631,10 +631,10 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { HistoryViewer: func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { mappingVersion := mapping.GroupVersionKind.GroupVersion() client, err := clients.ClientForVersion(&mappingVersion) - clientset := clientset.FromUnversionedClient(client) if err != nil { return nil, err } + clientset := clientset.FromUnversionedClient(client) return kubectl.HistoryViewerFor(mapping.GroupVersionKind.GroupKind(), clientset) }, Rollbacker: func(mapping *meta.RESTMapping) (kubectl.Rollbacker, error) { @@ -643,7 +643,8 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { if err != nil { return nil, err } - return kubectl.RollbackerFor(mapping.GroupVersionKind.GroupKind(), client) + clientset := clientset.FromUnversionedClient(client) + return kubectl.RollbackerFor(mapping.GroupVersionKind.GroupKind(), clientset) }, StatusViewer: func(mapping *meta.RESTMapping) (kubectl.StatusViewer, error) { mappingVersion := mapping.GroupVersionKind.GroupVersion() diff --git a/pkg/kubectl/rollback.go b/pkg/kubectl/rollback.go index f5294b0424..eb59575888 100644 --- a/pkg/kubectl/rollback.go +++ b/pkg/kubectl/rollback.go @@ -17,6 +17,7 @@ limitations under the License. package kubectl import ( + "bytes" "fmt" "os" "os/signal" @@ -25,18 +26,19 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" - client "k8s.io/kubernetes/pkg/client/unversioned" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" "k8s.io/kubernetes/pkg/runtime" + sliceutil "k8s.io/kubernetes/pkg/util/slice" "k8s.io/kubernetes/pkg/watch" ) // Rollbacker provides an interface for resources that can be rolled back. type Rollbacker interface { - Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64) (string, error) + Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) } -func RollbackerFor(kind unversioned.GroupKind, c client.Interface) (Rollbacker, error) { +func RollbackerFor(kind unversioned.GroupKind, c clientset.Interface) (Rollbacker, error) { switch kind { case extensions.Kind("Deployment"): return &DeploymentRollbacker{c}, nil @@ -45,14 +47,17 @@ func RollbackerFor(kind unversioned.GroupKind, c client.Interface) (Rollbacker, } type DeploymentRollbacker struct { - c client.Interface + c clientset.Interface } -func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64) (string, error) { +func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64, dryRun bool) (string, error) { d, ok := obj.(*extensions.Deployment) if !ok { return "", fmt.Errorf("passed object is not a Deployment: %#v", obj) } + if dryRun { + return simpleDryRun(d, r.c, toRevision) + } if d.Spec.Paused { return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume deployment/%s' and try again", d.Name) } @@ -66,7 +71,7 @@ func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations m result := "" // Get current events - events, err := r.c.Events(d.Namespace).List(api.ListOptions{}) + events, err := r.c.Core().Events(d.Namespace).List(api.ListOptions{}) if err != nil { return result, err } @@ -75,7 +80,7 @@ func (r *DeploymentRollbacker) Rollback(obj runtime.Object, updatedAnnotations m return result, err } // Watch for the changes of events - watch, err := r.c.Events(d.Namespace).Watch(api.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion}) + watch, err := r.c.Core().Events(d.Namespace).Watch(api.ListOptions{Watch: true, ResourceVersion: events.ResourceVersion}) if err != nil { return result, err } @@ -123,3 +128,50 @@ func isRollbackEvent(e *api.Event) (bool, string) { } return false, "" } + +func simpleDryRun(deployment *extensions.Deployment, c clientset.Interface, toRevision int64) (string, error) { + _, allOldRSs, newRS, err := deploymentutil.GetAllReplicaSets(deployment, c) + if err != nil { + return "", fmt.Errorf("failed to retrieve replica sets from deployment %s: %v", deployment.Name, err) + } + allRSs := allOldRSs + if newRS != nil { + allRSs = append(allRSs, newRS) + } + + revisionToSpec := make(map[int64]*api.PodTemplateSpec) + for _, rs := range allRSs { + v, err := deploymentutil.Revision(rs) + if err != nil { + continue + } + revisionToSpec[v] = &rs.Spec.Template + } + + if len(revisionToSpec) == 0 { + return "No rollout history found.", nil + } + + if toRevision > 0 { + template, ok := revisionToSpec[toRevision] + if !ok { + return "", fmt.Errorf("unable to find specified revision") + } + buf := bytes.NewBuffer([]byte{}) + DescribePodTemplate(template, buf) + return buf.String(), nil + } + + // Sort the revisionToSpec map by revision + revisions := make([]int64, 0, len(revisionToSpec)) + for r := range revisionToSpec { + revisions = append(revisions, r) + } + sliceutil.SortInts64(revisions) + + template, _ := revisionToSpec[revisions[len(revisions)-1]] + buf := bytes.NewBuffer([]byte{}) + buf.WriteString("\n") + DescribePodTemplate(template, buf) + return buf.String(), nil +}